summaryrefslogtreecommitdiff
path: root/modules/mono
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono')
-rw-r--r--modules/mono/SCsub2
-rw-r--r--modules/mono/build_scripts/godot_tools_build.py8
-rw-r--r--modules/mono/build_scripts/mono_configure.py14
-rw-r--r--modules/mono/build_scripts/solution_builder.py154
-rw-r--r--modules/mono/class_db_api_json.cpp12
-rw-r--r--modules/mono/config.py3
-rw-r--r--modules/mono/csharp_script.cpp717
-rw-r--r--modules/mono/csharp_script.h278
-rw-r--r--modules/mono/doc_classes/@C#.xml13
-rw-r--r--modules/mono/doc_classes/CSharpScript.xml5
-rw-r--r--modules/mono/doc_classes/GodotSharp.xml17
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln16
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj35
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec22
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props112
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets17
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs14
-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.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj39
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs17
-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/GodotTools.ProjectEditor.csproj60
-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.cs121
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs157
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs319
-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.sln8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs80
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs65
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs91
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs87
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildTab.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs116
-rwxr-xr-xmodules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs160
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj130
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs2
-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.cs377
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs12
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs7
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs10
-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.cs51
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/packages.config5
-rw-r--r--modules/mono/editor/bindings_generator.cpp548
-rw-r--r--modules/mono/editor/bindings_generator.h235
-rw-r--r--modules/mono/editor/code_completion.cpp250
-rw-r--r--modules/mono/editor/code_completion.h (renamed from modules/mono/editor/csharp_project.h)28
-rw-r--r--modules/mono/editor/csharp_project.cpp69
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp11
-rw-r--r--modules/mono/editor/godotsharp_export.cpp83
-rw-r--r--modules/mono/editor/script_class_parser.cpp82
-rw-r--r--modules/mono/editor/script_class_parser.h2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs234
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs292
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs602
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs17
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs329
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs49
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs148
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs232
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs185
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs163
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs18
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs144
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs180
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs342
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs159
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs355
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs143
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj35
-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.h5
-rw-r--r--modules/mono/glue/base_object_glue.cpp12
-rw-r--r--modules/mono/glue/base_object_glue.h73
-rw-r--r--modules/mono/glue/collections_glue.cpp5
-rw-r--r--modules/mono/glue/collections_glue.h124
-rw-r--r--modules/mono/glue/gd_glue.cpp17
-rw-r--r--modules/mono/glue/gd_glue.h86
-rw-r--r--modules/mono/glue/glue_header.h18
-rw-r--r--modules/mono/glue/nodepath_glue.cpp9
-rw-r--r--modules/mono/glue/nodepath_glue.h68
-rw-r--r--modules/mono/glue/rid_glue.cpp9
-rw-r--r--modules/mono/glue/rid_glue.h53
-rw-r--r--modules/mono/glue/scene_tree_glue.cpp (renamed from modules/mono/glue/string_name_glue.h)56
-rw-r--r--modules/mono/glue/string_glue.cpp4
-rw-r--r--modules/mono/glue/string_glue.h56
-rw-r--r--modules/mono/glue/string_name_glue.cpp7
-rw-r--r--modules/mono/godotsharp_dirs.cpp1
-rw-r--r--modules/mono/managed_callable.cpp7
-rw-r--r--modules/mono/mono_gc_handle.cpp2
-rw-r--r--modules/mono/mono_gc_handle.h10
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp224
-rw-r--r--modules/mono/mono_gd/gd_mono.h51
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.cpp157
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.h17
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp6
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_class.cpp106
-rw-r--r--modules/mono/mono_gd/gd_mono_class.h7
-rw-r--r--modules/mono/mono_gd/gd_mono_field.cpp15
-rw-r--r--modules/mono/mono_gd/gd_mono_field.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_internals.cpp8
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp20
-rw-r--r--modules/mono/mono_gd/gd_mono_log.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.cpp350
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.h10
-rw-r--r--modules/mono/mono_gd/gd_mono_method.cpp19
-rw-r--r--modules/mono/mono_gd/gd_mono_method.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_method_thunk.h28
-rw-r--r--modules/mono/mono_gd/gd_mono_property.cpp18
-rw-r--r--modules/mono/mono_gd/gd_mono_property.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp46
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h8
-rw-r--r--modules/mono/mono_gd/managed_type.h9
-rw-r--r--[-rwxr-xr-x]modules/mono/mono_gd/support/android_support.cpp0
-rw-r--r--[-rwxr-xr-x]modules/mono/mono_gd/support/ios_support.mm0
-rw-r--r--modules/mono/register_types.cpp6
-rw-r--r--modules/mono/signal_awaiter_utils.cpp19
-rw-r--r--modules/mono/utils/macros.h2
-rw-r--r--modules/mono/utils/mono_reg_utils.cpp8
-rw-r--r--modules/mono/utils/mono_reg_utils.h1
-rw-r--r--modules/mono/utils/osx_utils.cpp24
-rw-r--r--modules/mono/utils/path_utils.cpp46
-rw-r--r--modules/mono/utils/path_utils.h2
-rw-r--r--modules/mono/utils/string_utils.cpp24
175 files changed, 8360 insertions, 5201 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index c723b210cb..e8f3174a0a 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -29,7 +29,7 @@ if env_mono["tools"] or env_mono["target"] != "release":
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
diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py
index cffacf2577..3bbbf29d3b 100644
--- a/modules/mono/build_scripts/godot_tools_build.py
+++ b/modules/mono/build_scripts/godot_tools_build.py
@@ -13,13 +13,11 @@ def build_godot_tools(source, target, env):
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
+ extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]]
- nuget_restore(env, solution_path)
- build_solution(env, solution_path, build_config, extra_build_args)
+ build_solution(env, solution_path, build_config, extra_msbuild_args)
# No need to copy targets. The GodotTools csproj takes care of copying them.
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index 23f01b3cca..3e771e06f0 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -125,7 +125,8 @@ def configure(env, env_mono):
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"
+ "WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the"
+ " 'mono_prefix' SCons parameter instead"
)
# Although we don't support building with tools for any platform where we currently use static AOT,
@@ -191,17 +192,16 @@ def configure(env, env_mono):
env.Append(LIBS=["psapi"])
env.Append(LIBS=["version"])
else:
- mono_lib_name = find_name_in_dir_files(
- mono_lib_path, mono_lib_names, prefixes=["", "lib"], extensions=lib_suffixes
- )
+ mono_lib_file = find_file_in_dir(mono_lib_path, mono_lib_names, extensions=lib_suffixes)
- if not mono_lib_name:
+ 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")
+ 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")
diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py
index db6b4ff7aa..03f4e57f02 100644
--- a/modules/mono/build_scripts/solution_builder.py
+++ b/modules/mono/build_scripts/solution_builder.py
@@ -4,91 +4,41 @@ 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":
+ windows_exts = os.environ["PATHEXT"]
+ windows_exts = windows_exts.split(os.pathsep) if windows_exts 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
+ 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"]
+ hint_dirs = []
if sys.platform == "darwin":
- hint_dirs = [
+ hint_dirs[:0] = [
"/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, 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"):
@@ -96,7 +46,7 @@ def find_msbuild_unix(filename):
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):
@@ -158,21 +108,6 @@ 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"]
@@ -183,38 +118,31 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
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 xbuild_fallback and os.name == "nt":
- print("Option 'xbuild_fallback' not supported on Windows")
- xbuild_fallback = False
-
- if xbuild_fallback:
- print("Cannot find MSBuild executable, trying with xbuild")
- print("Warning: xbuild is deprecated")
+ msbuild_args = []
- msbuild_path = find_msbuild_unix("xbuild")
+ dotnet_cli = find_dotnet_cli()
- if msbuild_path is None:
- raise RuntimeError("Cannot find xbuild executable")
- else:
+ 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)
# 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")
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
index 384685d04b..d7b2028204 100644
--- a/modules/mono/class_db_api_json.cpp
+++ b/modules/mono/class_db_api_json.cpp
@@ -45,18 +45,17 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
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;
@@ -70,13 +69,13 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
k = nullptr;
while ((k = t->method_map.next(k))) {
-
String name = k->operator String();
ERR_CONTINUE(name.empty());
- if (name[0] == '_')
+ if (name[0] == '_') {
continue; // Ignore non-virtual methods that start with an underscore
+ }
snames.push_back(*k);
}
@@ -135,7 +134,6 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
k = nullptr;
while ((k = t->constant_map.next(k))) {
-
snames.push_back(*k);
}
@@ -163,7 +161,6 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
k = nullptr;
while ((k = t->signal_map.next(k))) {
-
snames.push_back(*k);
}
@@ -199,7 +196,6 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
k = nullptr;
while ((k = t->property_setget.next(k))) {
-
snames.push_back(*k);
}
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 106ca6e028..cd659057ef 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -30,12 +30,12 @@ def configure(env):
)
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", False
)
)
- envvars.Add(BoolVariable("xbuild_fallback", "If MSBuild is not found, fallback to xbuild", False))
# TODO: It would be great if this could be detected automatically instead
envvars.Add(
@@ -57,7 +57,6 @@ def configure(env):
def get_doc_classes():
return [
- "@C#",
"CSharpScript",
"GodotSharp",
]
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index f5911275c9..bbdec224f0 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -44,7 +44,6 @@
#ifdef TOOLS_ENABLED
#include "editor/bindings_generator.h"
-#include "editor/csharp_project.h"
#include "editor/editor_node.h"
#include "editor/node_dock.h"
#endif
@@ -67,7 +66,6 @@
#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();
@@ -85,28 +83,23 @@ static bool _create_project_solution_if_needed() {
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);
@@ -131,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);
@@ -140,9 +134,13 @@ void CSharpLanguage::init() {
}
void CSharpLanguage::finish() {
+ finalize();
+}
- if (finalized)
+void CSharpLanguage::finalize() {
+ if (finalized) {
return;
+ }
finalizing = true;
@@ -184,7 +182,6 @@ void CSharpLanguage::finish() {
}
void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
-
static const char *_reserved_words[] = {
// Reserved keywords
"abstract",
@@ -295,7 +292,7 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
"when",
"where",
"yield",
- 0
+ nullptr
};
const char **w = _reserved_words;
@@ -307,13 +304,11 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
}
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
// Verbatim string literals (`@" "`) don't render correctly, so don't highlight them.
@@ -321,7 +316,6 @@ void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const {
}
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;
@@ -330,7 +324,6 @@ 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"
@@ -366,12 +359,10 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin
}
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);
src = src.replace("%BASE%", base_class_name)
@@ -381,7 +372,6 @@ void CSharpLanguage::make_template(const String &p_class_name, const String &p_b
}
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);
@@ -392,32 +382,30 @@ 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.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::FLOAT)) {
#ifdef REAL_T_IS_DOUBLE
@@ -427,36 +415,49 @@ 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::PACKED_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::PACKED_INT32_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::PACKED_INT64_ARRAY))
+ }
+ 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))
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) {
return "float[]";
- if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT64_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))
+ }
+ 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::PACKED_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::PACKED_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::PACKED_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))
+ if (p_var_type_name == Variant::get_type_name(Variant::SIGNAL)) {
return "SignalInfo";
+ }
Variant::Type var_types[] = {
Variant::BOOL,
@@ -481,8 +482,9 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
};
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";
@@ -496,8 +498,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));
}
@@ -531,60 +534,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
// Printing an error here will result in endless recursion, so we must be careful
static thread_local bool _recursion_flag_ = false;
- if (_recursion_flag_)
+ 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());
@@ -604,11 +607,11 @@ 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_)
+ if (_recursion_flag_) {
return Vector<StackInfo>();
+ }
_recursion_flag_ = true;
SCOPE_EXIT { _recursion_flag_ = false; };
@@ -625,8 +628,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);
@@ -672,13 +676,13 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) {
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() != nullptr) {
const Ref<MonoGCHandleRef> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle;
@@ -698,11 +702,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) {
@@ -718,7 +722,6 @@ struct CSharpScriptDepSort {
};
void CSharpLanguage::reload_all_scripts() {
-
#ifdef GD_MONO_HOT_RELOAD
if (is_assembly_reloading_needed()) {
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -728,7 +731,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());
@@ -747,9 +749,9 @@ 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();
@@ -767,24 +769,27 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
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.
@@ -850,7 +855,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
to_reload.push_back(script);
if (script->get_path().empty()) {
- script->tied_class_name_for_reload = script->script_class->get_name();
+ script->tied_class_name_for_reload = script->script_class->get_name_for_lookup();
script->tied_class_namespace_for_reload = script->script_class->get_namespace();
}
@@ -890,8 +895,9 @@ 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;
@@ -926,8 +932,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
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();
@@ -1124,8 +1131,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
// Call OnAfterDeserialization
- if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
- obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
+ if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) {
+ obj->get_script_instance()->call(string_names.on_after_deserialize);
+ }
}
}
@@ -1173,7 +1181,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
#endif
void CSharpLanguage::_load_scripts_metadata() {
-
scripts_metadata.clear();
String scripts_metadata_filename = "scripts_metadata.";
@@ -1218,24 +1225,20 @@ void CSharpLanguage::_load_scripts_metadata() {
}
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();
@@ -1244,7 +1247,6 @@ void CSharpLanguage::thread_enter() {
}
void CSharpLanguage::thread_exit() {
-
#if 0
if (gdmono->is_runtime_initialized()) {
GDMonoUtils::detach_current_thread();
@@ -1253,7 +1255,6 @@ 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 (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = p_line;
@@ -1267,7 +1268,6 @@ 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 (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
@@ -1286,6 +1286,7 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
script_binding.inited = false;
}
+#ifdef GD_MONO_HOT_RELOAD
{
MutexLock lock(ManagedCallable::instances_mutex);
@@ -1295,13 +1296,13 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
managed_callable->delegate_invoke = nullptr;
}
}
+#endif
scripts_metadata_invalidated = true;
}
#ifdef TOOLS_ENABLED
void CSharpLanguage::_editor_init_callback() {
-
register_editor_internal_calls();
// Initialize GodotSharpEditor
@@ -1316,7 +1317,8 @@ void CSharpLanguage::_editor_init_callback() {
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));
+ 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
@@ -1328,13 +1330,11 @@ 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(MonoGCHandleData &p_gchandle) {
-
if (!p_gchandle.is_released()) { // Do not lock unnecessarily
MutexLock lock(get_singleton()->script_gchandle_release_mutex);
p_gchandle.release();
@@ -1342,7 +1342,6 @@ void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) {
}
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
@@ -1363,19 +1362,16 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, MonoGCH
}
CSharpLanguage::CSharpLanguage() {
-
ERR_FAIL_COND_MSG(singleton, "C# singleton already exist.");
singleton = this;
}
CSharpLanguage::~CSharpLanguage() {
-
- finish();
+ 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()) {
@@ -1388,8 +1384,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;
@@ -1424,28 +1421,27 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
}
void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
-
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))
+ 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() == nullptr) {
#ifdef DEBUG_ENABLED
CRASH_COND(!script_bindings.empty());
@@ -1454,8 +1450,9 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
return;
}
- if (finalizing)
+ if (finalizing) {
return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
+ }
GD_MONO_ASSERT_THREAD_ATTACHED;
@@ -1481,7 +1478,6 @@ 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);
#ifdef DEBUG_ENABLED
@@ -1495,8 +1491,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
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
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -1506,8 +1503,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
// so the owner must hold the managed side alive again to avoid it from being GCed.
MonoObject *target = gchandle.get_target();
- if (!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.
MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target);
@@ -1517,7 +1515,6 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
}
bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
-
Reference *ref_owner = Object::cast_to<Reference>(p_object);
#ifdef DEBUG_ENABLED
@@ -1533,8 +1530,9 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
int refcount = ref_owner->reference_get_count();
- if (!script_binding.inited)
+ if (!script_binding.inited) {
return refcount == 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;
@@ -1543,8 +1541,9 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
// the managed instance takes responsibility of deleting the owner when GCed.
MonoObject *target = gchandle.get_target();
- if (!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.
MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target);
@@ -1558,7 +1557,6 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
}
CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) {
-
CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script)));
Reference *ref = Object::cast_to<Reference>(p_owner);
@@ -1567,8 +1565,9 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS
instance->owner = p_owner;
instance->gchandle = p_gchandle;
- if (instance->base_ref)
+ if (instance->base_ref) {
instance->_reference_owner_unsafe();
+ }
p_script->instances.insert(p_owner);
@@ -1576,7 +1575,6 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS
}
MonoObject *CSharpInstance::get_mono_object() const {
-
ERR_FAIL_COND_V(gchandle.is_released(), nullptr);
return gchandle.get_target();
}
@@ -1586,7 +1584,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;
@@ -1627,8 +1624,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;
}
@@ -1640,7 +1638,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;
@@ -1704,7 +1701,6 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
}
void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) {
-
List<PropertyInfo> pinfo;
get_property_list(&pinfo);
@@ -1715,8 +1711,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();
@@ -1729,7 +1726,6 @@ void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Va
}
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);
@@ -1737,8 +1733,9 @@ void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName,
const CSharpScript::EventSignal &event_signal = E->value();
MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed);
- if (!delegate_field_value)
+ if (!delegate_field_value) {
continue; // Empty
+ }
Array serialized_data;
MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
@@ -1760,7 +1757,6 @@ void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName,
}
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());
}
@@ -1784,8 +1780,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;
}
@@ -1797,23 +1794,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;
@@ -1831,7 +1829,6 @@ bool CSharpInstance::has_method(const StringName &p_method) const {
}
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;
@@ -1868,46 +1865,7 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args,
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 == nullptr);
@@ -1929,14 +1887,14 @@ bool CSharpInstance::_reference_owner_unsafe() {
}
bool CSharpInstance::_unreference_owner_unsafe() {
-
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
CRASH_COND(owner == nullptr);
#endif
- if (!unsafe_referenced)
+ if (!unsafe_referenced) {
return false; // Already unreferenced
+ }
unsafe_referenced = false;
@@ -1969,7 +1927,7 @@ 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 = nullptr;
@@ -1979,8 +1937,9 @@ MonoObject *CSharpInstance::_internal_new_managed() {
// Tie managed to unmanaged
gchandle = MonoGCHandleData::new_strong_handle(mono_object);
- if (base_ref)
+ if (base_ref) {
_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
+ }
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner);
@@ -1991,7 +1950,6 @@ MonoObject *CSharpInstance::_internal_new_managed() {
}
void CSharpInstance::mono_object_disposed(MonoObject *p_obj) {
-
disconnect_event_signals();
#ifdef DEBUG_ENABLED
@@ -2002,7 +1960,6 @@ void CSharpInstance::mono_object_disposed(MonoObject *p_obj) {
}
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_released());
@@ -2063,7 +2020,6 @@ void CSharpInstance::disconnect_event_signals() {
}
void CSharpInstance::refcount_incremented() {
-
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
CRASH_COND(owner == nullptr);
@@ -2086,7 +2042,6 @@ void CSharpInstance::refcount_incremented() {
}
bool CSharpInstance::refcount_decremented() {
-
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
CRASH_COND(owner == nullptr);
@@ -2156,7 +2111,6 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab
}
void CSharpInstance::notification(int p_notification) {
-
GD_MONO_SCOPE_THREAD_ATTACH;
if (p_notification == Object::NOTIFICATION_PREDELETE) {
@@ -2195,7 +2149,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();
@@ -2203,7 +2156,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);
@@ -2227,8 +2180,9 @@ String CSharpInstance::to_string(bool *r_valid) {
MonoObject *mono_object = get_mono_object();
if (mono_object == nullptr) {
- if (r_valid)
+ if (r_valid) {
*r_valid = false;
+ }
return String();
}
@@ -2237,14 +2191,16 @@ String CSharpInstance::to_string(bool *r_valid) {
if (exc) {
GDMonoUtils::set_pending_exception(exc);
- if (r_valid)
+ if (r_valid) {
*r_valid = false;
+ }
return String();
}
if (result == nullptr) {
- if (r_valid)
+ if (r_valid) {
*r_valid = false;
+ }
return String();
}
@@ -2252,12 +2208,10 @@ String CSharpInstance::to_string(bool *r_valid) {
}
Ref<Script> CSharpInstance::get_script() const {
-
return script;
}
ScriptLanguage *CSharpInstance::get_language() {
-
return CSharpLanguage::get_singleton();
}
@@ -2266,7 +2220,6 @@ CSharpInstance::CSharpInstance(const Ref<CSharpScript> &p_script) :
}
CSharpInstance::~CSharpInstance() {
-
GD_MONO_SCOPE_THREAD_ATTACH;
destructing_script_instance = true;
@@ -2309,7 +2262,7 @@ CSharpInstance::~CSharpInstance() {
// 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 == nullptr);
@@ -2348,14 +2301,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);
}
@@ -2370,7 +2321,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;
@@ -2419,60 +2369,71 @@ void CSharpScript::_update_member_info_no_exports() {
#endif
bool CSharpScript::_update_exports() {
-
#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;
+
+ if (is_editor) {
+ exports_invalidated = false;
- MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
+ exported_members_cache.clear();
+ exported_members_defval_cache.clear();
- if (!tmp_object) {
- ERR_PRINT("Failed to allocate temporary MonoObject.");
- return false;
- }
+ // 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());
- uint32_t tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed)
+ if (!tmp_object) {
+ ERR_PRINT("Failed to allocate temporary MonoObject.");
+ return false;
+ }
- GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
+ tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed)
- ERR_FAIL_NULL_V_MSG(ctor, false,
- "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'.");
+ GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
- MonoException *ctor_exc = nullptr;
- ctor->invoke(tmp_object, nullptr, &ctor_exc);
+ ERR_FAIL_NULL_V_MSG(ctor, false,
+ "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'.");
- Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object));
+ MonoException *ctor_exc = nullptr;
+ ctor->invoke(tmp_object, nullptr, &ctor_exc);
- if (ctor_exc) {
- // TODO: Should we free 'tmp_native' if the exception was thrown after its creation?
+ tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object));
- GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
- tmp_object = nullptr;
+ if (ctor_exc) {
+ // TODO: Should we free 'tmp_native' if the exception was thrown after its creation?
- ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
- GDMonoUtils::debug_print_unhandled_exception(ctor_exc);
- return false;
+ 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;
@@ -2488,15 +2449,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
}
}
}
@@ -2509,22 +2477,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 = 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);
+#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
}
}
}
@@ -2532,52 +2506,57 @@ 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) != nullptr;
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ // Need to check this here, before disposal
+ bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr;
- // Dispose the temporary managed instance
+ // Dispose the temporary managed instance
- MonoException *exc = nullptr;
- 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);
+ }
- GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
- tmp_object = nullptr;
+ 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_PRINT("Temporary instance was added to the scene tree.");
- } else {
- memdelete(tmp_native);
+ if (tmp_native && !base_ref) {
+ 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;
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ placeholder_fallback_enabled = false;
- if (placeholders.size()) {
- // Update placeholders if any
- Map<StringName, Variant> values;
- List<PropertyInfo> propnames;
- _update_exports_values(values, propnames);
+ 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);
+ for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) {
+ E->get()->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;
@@ -2595,8 +2574,9 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati
for (int i = delegates.size() - 1; i >= 0; --i) {
GDMonoClass *delegate = delegates[i];
- if (!delegate->has_attribute(CACHED_CLASS(SignalAttribute)))
+ 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()));
@@ -2629,11 +2609,13 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati
GDMonoClass *field_class = field->get_type().type_class;
- if (!mono_class_is_delegate(field_class->get_mono_ptr()))
+ if (!mono_class_is_delegate(field_class->get_mono_ptr())) {
continue;
+ }
- if (!found_event_signals.find(field->get_name()))
+ 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()));
@@ -2679,13 +2661,11 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_in
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.
@@ -2693,13 +2673,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)))
+#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;
@@ -2716,13 +2700,19 @@ 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)
+#ifdef TOOLS_ENABLED
+ if (exported) {
ERR_PRINT("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ }
+#endif
return false;
}
if (!property->has_setter()) {
- if (exported)
+#ifdef TOOLS_ENABLED
+ if (exported) {
ERR_PRINT("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ }
+#endif
return false;
}
}
@@ -2736,16 +2726,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 && !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,
@@ -2756,6 +2751,7 @@ 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;
@@ -2772,8 +2768,8 @@ 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;
@@ -2844,8 +2840,9 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
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);
@@ -2872,18 +2869,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
}
#endif
-void CSharpScript::_clear() {
-
- tool = false;
- valid = false;
-
- base = nullptr;
- native = nullptr;
- script_class = nullptr;
-}
-
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 = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
@@ -2915,18 +2901,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;
}
@@ -2935,9 +2914,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;
@@ -2947,17 +2924,14 @@ 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 == nullptr);
@@ -2971,7 +2945,6 @@ 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 == nullptr);
@@ -2984,8 +2957,9 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon
GDMonoClass *base = p_script->script_class->get_parent_class();
- if (base != p_script->native)
+ 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));
@@ -3011,8 +2985,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();
}
@@ -3035,7 +3010,6 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon
}
bool CSharpScript::can_instance() const {
-
#ifdef TOOLS_ENABLED
bool extra_cond = tool || ScriptServer::is_scripting_enabled();
#else
@@ -3059,15 +3033,14 @@ 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, Callable::CallError &r_error) {
-
GD_MONO_ASSERT_THREAD_ATTACHED;
/* STEP 1, CREATE */
@@ -3127,7 +3100,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
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(nullptr);
r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
@@ -3137,8 +3110,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
// Tie managed to unmanaged
instance->gchandle = MonoGCHandleData::new_strong_handle(mono_object);
- if (instance->base_ref)
+ if (instance->base_ref) {
instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
+ }
{
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
@@ -3157,7 +3131,6 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
}
Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
-
if (!valid) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return Variant();
@@ -3193,7 +3166,6 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal
}
ScriptInstance *CSharpScript::instance_create(Object *p_this) {
-
#ifdef DEBUG_ENABLED
CRASH_COND(!valid);
#endif
@@ -3218,7 +3190,6 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
}
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);
@@ -3230,25 +3201,22 @@ PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_t
}
bool CSharpScript::instance_has(const Object *p_this) const {
-
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
return instances.has((Object *)p_this);
}
bool CSharpScript::has_source_code() const {
-
return !source.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;
@@ -3256,9 +3224,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;
@@ -3270,9 +3238,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;
@@ -3280,9 +3248,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;
@@ -3301,7 +3269,6 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
}
Error CSharpScript::reload(bool p_keep_state) {
-
bool has_instances;
{
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
@@ -3323,9 +3290,7 @@ Error CSharpScript::reload(bool p_keep_state) {
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);
+ if (klass && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) {
script_class = klass;
}
} else {
@@ -3359,8 +3324,9 @@ Error CSharpScript::reload(bool p_keep_state) {
GDMonoClass *base_class = script_class->get_parent_class();
- if (base_class != native)
+ if (base_class != native) {
base = base_class;
+ }
#ifdef DEBUG_ENABLED
// For debug builds, we must fetch from all native base methods as well.
@@ -3372,8 +3338,9 @@ Error CSharpScript::reload(bool p_keep_state) {
while (native_top) {
native_top->fetch_methods_with_godot_api_checks(native);
- if (native_top == CACHED_CLASS(GodotObject))
+ if (native_top == CACHED_CLASS(GodotObject)) {
break;
+ }
native_top = native_top->get_parent_class();
}
@@ -3463,12 +3430,10 @@ Error CSharpScript::reload(bool p_keep_state) {
}
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);
@@ -3486,7 +3451,6 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari
}
void CSharpScript::update_exports() {
-
#ifdef TOOLS_ENABLED
_update_exports();
#endif
@@ -3497,7 +3461,6 @@ bool CSharpScript::has_script_signal(const StringName &p_signal) const {
}
void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
-
for (const Map<StringName, Vector<SignalParameter>>::Element *E = _signals.front(); E; E = E->next()) {
MethodInfo mi;
mi.name = E->key();
@@ -3507,8 +3470,9 @@ void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
const SignalParameter &param = params[i];
PropertyInfo arg_info = PropertyInfo(param.type, param.name);
- if (param.type == Variant::NIL && param.nil_is_variant)
+ if (param.type == Variant::NIL && param.nil_is_variant) {
arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
mi.arguments.push_back(arg_info);
}
@@ -3526,8 +3490,9 @@ void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
const SignalParameter &param = params[i];
PropertyInfo arg_info = PropertyInfo(param.type, param.name);
- if (param.type == Variant::NIL && param.nil_is_variant)
+ if (param.type == Variant::NIL && param.nil_is_variant) {
arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
mi.arguments.push_back(arg_info);
}
@@ -3542,45 +3507,52 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
return false;
}
-#ifndef _MSC_VER
-#warning TODO: Implement CSharpScript::inherits_script and other relevant changes after GH-38063.
-#endif
- 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;
}
MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const {
-
- if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute)))
+ if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) {
return MultiplayerAPI::RPC_MODE_REMOTE;
- if (p_member->has_attribute(CACHED_CLASS(MasterAttribute)))
+ }
+ if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) {
return MultiplayerAPI::RPC_MODE_MASTER;
- if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute)))
+ }
+ if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) {
return MultiplayerAPI::RPC_MODE_PUPPET;
- if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute)))
+ }
+ if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) {
return MultiplayerAPI::RPC_MODE_REMOTESYNC;
- if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute)))
+ }
+ if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) {
return MultiplayerAPI::RPC_MODE_MASTERSYNC;
- if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute)))
+ }
+ if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute))) {
return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+ }
return MultiplayerAPI::RPC_MODE_DISABLED;
}
@@ -3640,7 +3612,6 @@ MultiplayerAPI::RPCMode CSharpScript::get_rset_mode(const StringName &p_variable
}
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,
@@ -3656,16 +3627,27 @@ 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.empty()) {
+ name = get_path().get_file().get_basename();
+ }
}
-CSharpScript::CSharpScript() {
+void CSharpScript::_clear() {
+ tool = false;
+ valid = false;
+ base = nullptr;
+ native = nullptr;
+ script_class = nullptr;
+}
+
+CSharpScript::CSharpScript() {
_clear();
- _resource_path_changed();
+ _update_name();
#ifdef DEBUG_ENABLED
{
@@ -3676,19 +3658,28 @@ CSharpScript::CSharpScript() {
}
CSharpScript::~CSharpScript() {
-
#ifdef DEBUG_ENABLED
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
CSharpLanguage::get_singleton()->script_list.remove(&this->script_list);
#endif
}
+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
+}
+
/*************** RESOURCE ***************/
RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) {
-
- if (r_error)
+ if (r_error) {
*r_error = ERR_FILE_CANT_OPEN;
+ }
// TODO ignore anything inside bin/ and obj/ in tools builds?
@@ -3705,29 +3696,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);
@@ -3735,13 +3723,9 @@ 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 {
+ // 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 + "'.");
}
}
@@ -3771,19 +3755,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()) != nullptr;
}
CSharpLanguage::StringNameCache::StringNameCache() {
-
_signal_callback = StaticCString::create("_signal_callback");
_set = StaticCString::create("_set");
_get = StaticCString::create("_get");
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 05e2857538..f0b43a40f9 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -66,7 +66,6 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))
class CSharpScript : public Script {
-
GDCLASS(CSharpScript, Script);
public:
@@ -136,19 +135,26 @@ private:
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, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> &params);
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
@@ -165,72 +171,71 @@ private:
protected:
static void _bind_methods();
- Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::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_instance() 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;
- virtual bool has_source_code() const;
- virtual String get_source_code() const;
- virtual void set_source_code(const String &p_code);
+ bool has_source_code() const override;
+ String get_source_code() const override;
+ void set_source_code(const String &p_code) override;
- virtual Error reload(bool p_keep_state = false);
+ Error reload(bool p_keep_state = false) override;
- virtual bool has_script_signal(const StringName &p_signal) const;
- virtual void get_script_signal_list(List<MethodInfo> *r_signals) const;
+ bool has_script_signal(const StringName &p_signal) const override;
+ void get_script_signal_list(List<MethodInfo> *r_signals) const 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();
+ 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 is_tool() const { return tool; }
- virtual bool is_valid() const { return valid; }
+ void get_members(Set<StringName> *p_members) override;
- bool inherits_script(const Ref<Script> &p_script) const;
+ 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;
- virtual Vector<ScriptNetData> get_rpc_methods() const;
- virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
- virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
- virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
- virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+ int get_member_line(const StringName &p_member) const override;
- virtual Vector<ScriptNetData> get_rset_properties() const;
- virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
- virtual StringName get_rset_property(const uint16_t p_variable_id) const;
- virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const;
- virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+ Vector<ScriptNetData> get_rpc_methods() const override;
+ uint16_t get_rpc_method_id(const StringName &p_method) const override;
+ StringName get_rpc_method(const uint16_t p_rpc_method_id) const override;
+ MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const override;
+ MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const override;
+
+ Vector<ScriptNetData> get_rset_properties() const override;
+ uint16_t get_rset_property_id(const StringName &p_variable) const override;
+ StringName get_rset_property(const uint16_t p_variable_id) const override;
+ MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const override;
+ MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) 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;
@@ -244,8 +249,6 @@ class CSharpInstance : public ScriptInstance {
Ref<CSharpScript> script;
MonoGCHandleData gchandle;
- Vector<Callable> event_signal_callables;
-
bool _reference_owner_unsafe();
/*
@@ -262,8 +265,6 @@ class CSharpInstance : public ScriptInstance {
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle);
- void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
-
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);
@@ -272,18 +273,16 @@ public:
_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, Callable::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);
@@ -296,46 +295,42 @@ public:
void connect_event_signals();
void disconnect_event_signals();
- virtual void refcount_incremented();
- virtual bool refcount_decremented();
+ void refcount_incremented() override;
+ bool refcount_decremented() override;
- virtual Vector<ScriptNetData> get_rpc_methods() const;
- virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
- virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
- virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
- virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+ Vector<ScriptNetData> get_rpc_methods() const override;
+ uint16_t get_rpc_method_id(const StringName &p_method) const override;
+ StringName get_rpc_method(const uint16_t p_rpc_method_id) const override;
+ MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const override;
+ MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const override;
- virtual Vector<ScriptNetData> get_rset_properties() const;
- virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
- virtual StringName get_rset_property(const uint16_t p_variable_id) const;
- virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const;
- virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+ Vector<ScriptNetData> get_rset_properties() const override;
+ uint16_t get_rset_property_id(const StringName &p_variable) const override;
+ StringName get_rset_property(const uint16_t p_variable_id) const override;
+ MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const override;
+ MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) 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(const Ref<CSharpScript> &p_script);
~CSharpInstance();
};
struct CSharpScriptBinding {
- bool inited;
+ bool inited = false;
StringName type_name;
- GDMonoClass *wrapper_class;
+ GDMonoClass *wrapper_class = nullptr;
MonoGCHandleData gchandle;
- Object *owner;
+ Object *owner = nullptr;
- CSharpScriptBinding() :
- inited(false),
- wrapper_class(nullptr),
- owner(nullptr) {
- }
+ CSharpScriptBinding() {}
};
class ManagedCallableMiddleman : public Object {
@@ -343,7 +338,6 @@ class ManagedCallableMiddleman : public Object {
};
class CSharpLanguage : public ScriptLanguage {
-
friend class CSharpScript;
friend class CSharpInstance;
@@ -370,7 +364,6 @@ class CSharpLanguage : public ScriptLanguage {
ManagedCallableMiddleman *managed_callable_middleman = memnew(ManagedCallableMiddleman);
struct StringNameCache {
-
StringName _signal_callback;
StringName _set;
StringName _get;
@@ -438,83 +431,90 @@ public:
}
_FORCE_INLINE_ const Dictionary &get_scripts_metadata() {
- if (scripts_metadata_invalidated)
+ if (scripts_metadata_invalidated) {
_load_scripts_metadata();
+ }
return scripts_metadata;
}
_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
- 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 = nullptr, Set<int> *r_safe_lines = nullptr) 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 PackedStringArray &p_args) const;
+ void get_reserved_words(List<String> *p_words) 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, 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 = 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);
@@ -532,17 +532,17 @@ public:
class ResourceFormatLoaderCSharpScript : public ResourceFormatLoader {
public:
- virtual 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, bool p_no_cache = false);
- 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, bool p_no_cache = false) 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 83a7fbf02c..0000000000
--- a/modules/mono/doc_classes/@C#.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="@C#" version="4.0">
- <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 1eb3404f9e..e1e9d1381f 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" 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>https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html</link>
</tutorials>
<methods>
<method name="new" qualifiers="vararg">
<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 19a08d2036..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" 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..56c0cb7703
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
@@ -0,0 +1,16 @@
+
+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
+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
+ 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..86a0a4393e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.Build.NoTargets/2.0.1">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+
+ <Description>MSBuild .NET Sdk for Godot projects.</Description>
+ <Authors>Godot Engine contributors</Authors>
+
+ <PackageId>Godot.NET.Sdk</PackageId>
+ <Version>4.0.0</Version>
+ <PackageVersion>4.0.0-dev2</PackageVersion>
+ <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
+ <PackageType>MSBuildSdk</PackageType>
+ <PackageTags>MSBuildSdk</PackageTags>
+ <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
+ <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
+ </PropertyGroup>
+
+ <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') ">
+ <PropertyGroup>
+ <NuspecProperties>
+ id=$(PackageId);
+ description=$(Description);
+ authors=$(Authors);
+ version=$(PackageVersion);
+ packagetype=$(PackageType);
+ tags=$(PackageTags);
+ projecturl=$(PackageProjectUrl)
+ </NuspecProperties>
+ </PropertyGroup>
+ </Target>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
new file mode 100644
index 0000000000..5b5cefe80e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
+ <metadata>
+ <id>$id$</id>
+ <version>$version$</version>
+ <description>$description$</description>
+ <authors>$authors$</authors>
+ <owners>$authors$</owners>
+ <projectUrl>$projecturl$</projectUrl>
+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
+ <license type="expression">MIT</license>
+ <licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
+ <tags>$tags$</tags>
+ <packageTypes>
+ <packageType name="$packagetype$" />
+ </packageTypes>
+ <repository url="$projecturl$" />
+ </metadata>
+ <files>
+ <file src="Sdk\**" target="Sdk" />\
+ </files>
+</package>
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..dfc59e6ccb
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -0,0 +1,112 @@
+<Project>
+ <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)\.mono\temp\'. -->
+ <BaseOutputPath>$(GodotProjectDir).mono\temp\bin\</BaseOutputPath>
+ <OutputPath>$(GodotProjectDir).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).mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
+ <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).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 differenciate between Debug and ExportDebug configurations. -->
+ <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants>
+
+ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
+ </PropertyGroup>
+
+ <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).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
+ </Reference>
+ <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
+ <Private>false</Private>
+ <HintPath>$(GodotProjectDir).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..f5afd75505
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -0,0 +1,17 @@
+<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>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
index 6015cb22b6..c2549b4ad5 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
{
@@ -183,4 +182,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 4374f21cfa..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
index 85760a3705..e1ccf0454a 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
@@ -19,7 +19,10 @@ namespace GodotTools.Core
}
if (attempt > maxAttempts + 1)
- return;
+ {
+ // 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 c9ea7d3a2c..d6d8962f90 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
@@ -1,40 +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="FileUtils.cs" />
- <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 326c49f096..012b69032e 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
@@ -25,31 +25,22 @@ namespace GodotTools.Core
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 + path : 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)
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 0806d02ca0..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..572c541412
--- /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, ".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/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
index b60e501beb..9cb50014b0 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
@@ -1,57 +1,23 @@
-<?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" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
- <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
- <Name>GodotTools.Core</Name>
- </ProjectReference>
+ <!--
+ 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.
+ -->
+ <None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
</ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</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 f0e0d1b33d..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-using GodotTools.Core;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using DotNet.Globbing;
-using Microsoft.Build.Construction;
-
-namespace GodotTools.ProjectEditor
-{
- public static class ProjectExtensions
- {
- public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
- {
- GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
-
- string normalizedInclude = include.NormalizePath();
-
- foreach (var itemGroup in root.ItemGroups)
- {
- if (noCondition && 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 item;
- }
- }
-
- return null;
- }
- public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
- {
- GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
-
- string normalizedInclude = Path.GetFullPath(include).NormalizePath();
-
- foreach (var itemGroup in root.ItemGroups)
- {
- if (noCondition && itemGroup.Condition.Length != 0)
- continue;
-
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- var glob = Glob.Parse(Path.GetFullPath(item.Include).NormalizePath(), globOptions);
-
- if (glob.IsMatch(normalizedInclude))
- return item;
- }
- }
-
- return null;
- }
-
- public static IEnumerable<ProjectItemElement> FindAllItemsInFolder(this ProjectRootElement root, string itemType, string folder)
- {
- string absFolderNormalizedWithSep = Path.GetFullPath(folder).NormalizePath() + Path.DirectorySeparatorChar;
-
- foreach (var itemGroup in root.ItemGroups)
- {
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
-
- if (absPathNormalized.StartsWith(absFolderNormalizedWithSep))
- yield return item;
- }
- }
- }
-
- public static bool HasItem(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
- {
- return root.FindItemOrNull(itemType, include, noCondition) != null;
- }
-
- public static bool AddItemChecked(this ProjectRootElement root, string itemType, string include)
- {
- if (!root.HasItem(itemType, include, noCondition: true))
- {
- root.AddItem(itemType, include);
- return true;
- }
-
- return false;
- }
-
- public static bool RemoveItemChecked(this ProjectRootElement root, string itemType, string include)
- {
- var item = root.FindItemOrNullAbs(itemType, include);
- if (item != null)
- {
- item.Parent.RemoveChild(item);
- 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 cbe3afaedd..5541876f9e 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,162 +1,49 @@
-using GodotTools.Core;
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Reflection;
using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
namespace GodotTools.ProjectEditor
{
public static class ProjectGenerator
{
- private const string CoreApiProjectName = "GodotSharp";
- private const string EditorApiProjectName = "GodotSharpEditor";
+ public const string GodotSdkVersionToUse = "4.0.0-dev2";
- public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
- {
- string path = Path.Combine(dir, name + ".csproj");
-
- ProjectPropertyGroupElement mainGroup;
- var root = CreateLibraryProject(name, "Debug", 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)' != 'ExportRelease' ";
- mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'ExportRelease' ";
-
- 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;TOOLS;");
- debugGroup.AddProperty("ErrorReport", "prompt");
- debugGroup.AddProperty("WarningLevel", "4");
- debugGroup.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)' == 'Debug' ";
- editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
- editorApiRef.AddMetadata("Private", "False");
-
- GenAssemblyInfoFile(root, dir, name);
+ public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GodotSdkVersionToUse}";
- 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)
+ public static ProjectRootElement GenGameProject(string name)
{
- string propertiesDir = Path.Combine(dir, "Properties");
- if (!Directory.Exists(propertiesDir))
- Directory.CreateDirectory(propertiesDir);
-
- string usingDirectivesText = string.Empty;
-
- if (usingDirectives != null)
- {
- foreach (var usingDirective in usingDirectives)
- usingDirectivesText += "\nusing " + usingDirective + ";";
- }
+ if (name.Length == 0)
+ throw new ArgumentException("Project name is empty", nameof(name));
- string assemblyLinesText = string.Empty;
+ var root = ProjectRootElement.Create(NewProjectFileOptions.None);
- if (assemblyLines != null)
- assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
+ root.Sdk = GodotSdkAttrValue;
- string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
+ var mainGroup = root.AddPropertyGroup();
+ mainGroup.AddProperty("TargetFramework", "netstandard2.1");
- string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
+ string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
- File.WriteAllText(assemblyInfoFile, content);
+ // If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
+ if (sanitizedName != name)
+ mainGroup.AddProperty("RootNamespace", sanitizedName);
- 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());
-
- var exportDebugGroup = root.AddPropertyGroup();
- exportDebugGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportDebug|AnyCPU' ";
- exportDebugGroup.AddProperty("DebugSymbols", "true");
- exportDebugGroup.AddProperty("DebugType", "portable");
- exportDebugGroup.AddProperty("Optimize", "false");
- exportDebugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;");
- exportDebugGroup.AddProperty("ErrorReport", "prompt");
- exportDebugGroup.AddProperty("WarningLevel", "4");
- exportDebugGroup.AddProperty("ConsolePause", "false");
+ if (name.Length == 0)
+ throw new ArgumentException("Project name is empty", nameof(name));
- var exportReleaseGroup = root.AddPropertyGroup();
- exportReleaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportRelease|AnyCPU' ";
- exportReleaseGroup.AddProperty("DebugType", "portable");
- exportReleaseGroup.AddProperty("Optimize", "true");
- exportReleaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;");
- exportReleaseGroup.AddProperty("ErrorReport", "prompt");
- exportReleaseGroup.AddProperty("WarningLevel", "4");
- exportReleaseGroup.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("/", "\\"));
+ root.Save(path);
- 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 f2ebef1a7d..4041c56597 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -1,17 +1,17 @@
+using System;
using GodotTools.Core;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Reflection;
-using DotNet.Globbing;
using Microsoft.Build.Construction;
+using Microsoft.Build.Globbing;
namespace GodotTools.ProjectEditor
{
public sealed class MSBuildProject
{
- public ProjectRootElement Root { get; }
+ internal ProjectRootElement Root { get; set; }
public bool HasUnsavedChanges { get; set; }
@@ -31,91 +31,7 @@ namespace GodotTools.ProjectEditor
return root != null ? new MSBuildProject(root) : null;
}
- 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);
-
- var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
-
- if (root.AddItemChecked(itemType, normalizedInclude))
- root.Save();
- }
-
- public static void RenameItemInProjectChecked(string projectPath, string itemType, string oldInclude, string newInclude)
- {
- var dir = Directory.GetParent(projectPath).FullName;
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- var normalizedOldInclude = oldInclude.NormalizePath();
- var normalizedNewInclude = newInclude.NormalizePath();
-
- var item = root.FindItemOrNullAbs(itemType, normalizedOldInclude);
-
- if (item == null)
- return;
-
- item.Include = normalizedNewInclude.RelativeToPath(dir).Replace("/", "\\");
- root.Save();
- }
-
- public static void RemoveItemFromProjectChecked(string projectPath, string itemType, string include)
- {
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- var normalizedInclude = include.NormalizePath();
-
- if (root.RemoveItemChecked(itemType, normalizedInclude))
- root.Save();
- }
-
- public static void RenameItemsToNewFolderInProjectChecked(string projectPath, string itemType, string oldFolder, string newFolder)
- {
- var dir = Directory.GetParent(projectPath).FullName;
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- bool dirty = false;
-
- var oldFolderNormalized = oldFolder.NormalizePath();
- var newFolderNormalized = newFolder.NormalizePath();
- string absOldFolderNormalized = Path.GetFullPath(oldFolderNormalized).NormalizePath();
- string absNewFolderNormalized = Path.GetFullPath(newFolderNormalized).NormalizePath();
-
- foreach (var item in root.FindAllItemsInFolder(itemType, oldFolderNormalized))
- {
- string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
- string absNewIncludeNormalized = absNewFolderNormalized + absPathNormalized.Substring(absOldFolderNormalized.Length);
- item.Include = absNewIncludeNormalized.RelativeToPath(dir).Replace("/", "\\");
- dirty = true;
- }
-
- if (dirty)
- root.Save();
- }
-
- public static void RemoveItemsInFolderFromProjectChecked(string projectPath, string itemType, string folder)
- {
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- var folderNormalized = folder.NormalizePath();
-
- var itemsToRemove = root.FindAllItemsInFolder(itemType, folderNormalized).ToList();
-
- if (itemsToRemove.Count > 0)
- {
- foreach (var item in itemsToRemove)
- item.Parent.RemoveChild(item);
-
- root.Save();
- }
- }
-
- private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
+ private static List<string> GetAllFilesRecursive(string rootDirectory, string mask)
{
string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
@@ -125,231 +41,60 @@ namespace GodotTools.ProjectEditor
files[i] = files[i].RelativeToPath(rootDirectory);
}
- return files;
+ return new List<string>(files);
}
- public static string[] GetIncludeFiles(string projectPath, string itemType)
+ // NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future.
+ public static IEnumerable<string> GetIncludeFiles(string projectPath, string itemType)
{
- var result = new List<string>();
- var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
-
- var globOptions = new GlobOptions();
- globOptions.Evaluation.CaseInsensitive = false;
+ var excluded = new List<string>();
+ var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
- foreach (var itemGroup in root.ItemGroups)
+ foreach (var item in root.Items)
{
- if (itemGroup.Condition.Length != 0)
+ if (string.IsNullOrEmpty(item.Condition))
continue;
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- string normalizedInclude = item.Include.NormalizePath();
+ if (item.ItemType != itemType)
+ continue;
- var glob = Glob.Parse(normalizedInclude, globOptions);
+ string normalizedExclude = item.Exclude.NormalizePath();
- // TODO Check somehow if path has no blob to avoid the following loop...
+ var glob = MSBuildGlob.Parse(normalizedExclude);
- foreach (var existingFile in existingFiles)
- {
- if (glob.IsMatch(existingFile))
- {
- result.Add(existingFile);
- }
- }
- }
+ excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile)));
}
- return result.ToArray();
+ includedFiles.RemoveAll(f => excluded.Contains(f));
+
+ return includedFiles;
}
- /// Simple function to make sure the Api assembly references are configured correctly
- public static void FixApiHintPath(MSBuildProject project)
+ public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
{
- var root = project.Root;
-
- void AddPropertyIfNotPresent(string name, string condition, string value)
- {
- if (root.PropertyGroups
- .Any(g => (g.Condition == string.Empty || g.Condition.Trim() == condition) &&
- g.Properties
- .Any(p => p.Name == name &&
- p.Value == value &&
- (p.Condition.Trim() == condition || g.Condition.Trim() == condition))))
- {
- return;
- }
-
- root.AddProperty(name, value).Condition = " " + condition + " ";
- project.HasUnsavedChanges = true;
- }
-
- AddPropertyIfNotPresent(name: "ApiConfiguration",
- condition: "'$(Configuration)' != 'ExportRelease'",
- value: "Debug");
- AddPropertyIfNotPresent(name: "ApiConfiguration",
- condition: "'$(Configuration)' == 'ExportRelease'",
- value: "Release");
-
- void SetReferenceHintPath(string referenceName, string condition, string hintPath)
- {
- foreach (var itemGroup in root.ItemGroups.Where(g =>
- g.Condition.Trim() == string.Empty || g.Condition.Trim() == condition))
- {
- var references = itemGroup.Items.Where(item =>
- item.ItemType == "Reference" &&
- item.Include == referenceName &&
- (item.Condition.Trim() == condition || itemGroup.Condition.Trim() == condition));
-
- var referencesWithHintPath = references.Where(reference =>
- reference.Metadata.Any(m => m.Name == "HintPath"));
-
- if (referencesWithHintPath.Any(reference => reference.Metadata
- .Any(m => m.Name == "HintPath" && m.Value == hintPath)))
- {
- // Found a Reference item with the right HintPath
- 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);
- project.HasUnsavedChanges = true;
- return;
- }
-
- var referenceWithoutHintPath = references.FirstOrDefault();
- if (referenceWithoutHintPath != null)
- {
- // Found a Reference item without a HintPath
- referenceWithoutHintPath.AddMetadata("HintPath", hintPath);
- project.HasUnsavedChanges = true;
- return;
- }
- }
-
- // Found no Reference item at all. Add it.
- root.AddItem("Reference", referenceName).Condition = " " + condition + " ";
- project.HasUnsavedChanges = true;
- }
-
- const string coreProjectName = "GodotSharp";
- const string editorProjectName = "GodotSharpEditor";
+ var origRoot = project.Root;
- const string coreCondition = "";
- const string editorCondition = "'$(Configuration)' == 'Debug'";
-
- var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll";
- var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll";
+ if (!string.IsNullOrEmpty(origRoot.Sdk))
+ return;
- SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath);
- SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath);
+ project.Root = ProjectGenerator.GenGameProject(projectName);
+ project.Root.FullPath = origRoot.FullPath;
+ project.HasUnsavedChanges = true;
}
- public static void MigrateFromOldConfigNames(MSBuildProject project)
+ public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
{
var root = project.Root;
+ string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
- bool hasGodotProjectGeneratorVersion = false;
- bool foundOldConfiguration = false;
-
- foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition == string.Empty))
- {
- if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion"))
- hasGodotProjectGeneratorVersion = true;
-
- foreach (var configItem in propertyGroup.Properties
- .Where(p => p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Tools"))
- {
- configItem.Value = "Debug";
- foundOldConfiguration = true;
- project.HasUnsavedChanges = true;
- }
- }
-
- if (!hasGodotProjectGeneratorVersion)
- {
- root.PropertyGroups.First(g => g.Condition == string.Empty)?
- .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
- project.HasUnsavedChanges = true;
- }
-
- if (!foundOldConfiguration)
- {
- var toolsConditions = new[]
- {
- "'$(Configuration)|$(Platform)' == 'Tools|AnyCPU'",
- "'$(Configuration)|$(Platform)' != 'Tools|AnyCPU'",
- "'$(Configuration)' == 'Tools'",
- "'$(Configuration)' != 'Tools'"
- };
-
- foundOldConfiguration = root.PropertyGroups
- .Any(g => toolsConditions.Any(c => c == g.Condition.Trim()));
- }
-
- if (foundOldConfiguration)
- {
- void MigrateConfigurationConditions(string oldConfiguration, string newConfiguration)
- {
- void MigrateConditions(string oldCondition, string newCondition)
- {
- foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition.Trim() == oldCondition))
- {
- propertyGroup.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
-
- foreach (var propertyGroup in root.PropertyGroups)
- {
- foreach (var prop in propertyGroup.Properties.Where(p => p.Condition.Trim() == oldCondition))
- {
- prop.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
- }
-
- foreach (var itemGroup in root.ItemGroups.Where(g => g.Condition.Trim() == oldCondition))
- {
- itemGroup.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
-
- foreach (var itemGroup in root.ItemGroups)
- {
- foreach (var item in itemGroup.Items.Where(item => item.Condition.Trim() == oldCondition))
- {
- item.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
- }
- }
-
- foreach (var op in new[] {"==", "!="})
- {
- MigrateConditions($"'$(Configuration)|$(Platform)' {op} '{oldConfiguration}|AnyCPU'", $"'$(Configuration)|$(Platform)' {op} '{newConfiguration}|AnyCPU'");
- MigrateConditions($"'$(Configuration)' {op} '{oldConfiguration}'", $"'$(Configuration)' {op} '{newConfiguration}'");
- }
- }
+ if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
+ return;
- MigrateConfigurationConditions("Debug", "ExportDebug");
- MigrateConfigurationConditions("Release", "ExportRelease");
- MigrateConfigurationConditions("Tools", "Debug"); // Must be last
- }
+ 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 3a0464c9bc..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.sln b/modules/mono/editor/GodotTools/GodotTools.sln
index a3438ea5f3..ba5379e562 100644
--- a/modules/mono/editor/GodotTools/GodotTools.sln
+++ b/modules/mono/editor/GodotTools/GodotTools.sln
@@ -9,7 +9,9 @@ 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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -37,5 +39,9 @@ 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
EndGlobalSection
EndGlobal
diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
index 3cf495f025..3ab669a9f3 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
@@ -20,52 +20,54 @@ namespace GodotTools
private ItemList buildTabsList;
private TabContainer buildTabs;
- private ToolButton warningsBtn;
- private ToolButton errorsBtn;
+ private Button warningsBtn;
+ private Button errorsBtn;
private Button viewLogBtn;
- private void _UpdateBuildTabsList()
+ private void _UpdateBuildTab(int index, int? currentTab)
{
- buildTabsList.Clear();
+ var tab = (BuildTab)buildTabs.GetChild(index);
- int currentTab = buildTabs.CurrentTab;
+ string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
+ itemName += " [" + tab.BuildInfo.Configuration + "]";
- bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
+ buildTabsList.AddItem(itemName, tab.IconTexture);
- for (int i = 0; i < buildTabs.GetChildCount(); i++)
- {
- var tab = (BuildTab)buildTabs.GetChild(i);
+ string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
+ itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
+ itemTooltip += "\nStatus: ";
- if (tab == null)
- continue;
+ if (tab.BuildExited)
+ itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
+ else
+ itemTooltip += "Running";
- string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
- itemName += " [" + tab.BuildInfo.Configuration + "]";
+ if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
+ itemTooltip += $"\nErrors: {tab.ErrorCount}";
- buildTabsList.AddItem(itemName, tab.IconTexture);
+ itemTooltip += $"\nWarnings: {tab.WarningCount}";
- string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
- itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
- itemTooltip += "\nStatus: ";
+ buildTabsList.SetItemTooltip(index, itemTooltip);
- if (tab.BuildExited)
- itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
- else
- itemTooltip += "Running";
+ // If this tab was already selected before the changes or if no tab was selected
+ if (currentTab == null || currentTab == index)
+ {
+ buildTabsList.Select(index);
+ _BuildTabsItemSelected(index);
+ }
+ }
- if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
- itemTooltip += $"\nErrors: {tab.ErrorCount}";
+ private void _UpdateBuildTabsList()
+ {
+ buildTabsList.Clear();
- itemTooltip += $"\nWarnings: {tab.WarningCount}";
+ int? currentTab = buildTabs.CurrentTab;
- buildTabsList.SetItemTooltip(i, itemTooltip);
+ if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+ currentTab = null;
- if (noCurrentTab || currentTab == i)
- {
- buildTabsList.Select(i);
- _BuildTabsItemSelected(i);
- }
- }
+ for (int i = 0; i < buildTabs.GetChildCount(); i++)
+ _UpdateBuildTab(i, currentTab);
}
public BuildTab GetBuildTabFor(BuildInfo buildInfo)
@@ -160,13 +162,7 @@ namespace GodotTools
}
}
- var godotDefines = new[]
- {
- OS.GetName(),
- Internal.GodotIs32Bits() ? "32" : "64"
- };
-
- bool buildSuccess = BuildManager.BuildProjectBlocking("Debug", godotDefines);
+ bool buildSuccess = BuildManager.BuildProjectBlocking("Debug");
if (!buildSuccess)
return;
@@ -272,7 +268,7 @@ namespace GodotTools
};
panelTabs.AddChild(panelBuildsTab);
- var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+ var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
panelBuildsTab.AddChild(toolBarHBox);
var buildProjectBtn = new Button
@@ -285,7 +281,7 @@ namespace GodotTools
toolBarHBox.AddSpacer(begin: false);
- warningsBtn = new ToolButton
+ warningsBtn = new Button
{
Text = "Warnings".TTR(),
ToggleMode = true,
@@ -296,7 +292,7 @@ namespace GodotTools
warningsBtn.Toggled += _WarningsToggled;
toolBarHBox.AddChild(warningsBtn);
- errorsBtn = new ToolButton
+ errorsBtn = new Button
{
Text = "Errors".TTR(),
ToggleMode = true,
@@ -325,7 +321,7 @@ namespace GodotTools
};
panelBuildsTab.AddChild(hsc);
- buildTabsList = new ItemList { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+ buildTabsList = new ItemList {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
buildTabsList.ItemSelected += _BuildTabsItemSelected;
buildTabsList.NothingSelected += _BuildTabsNothingSelected;
hsc.AddChild(buildTabsList);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 43c96d2e30..d9862ae361 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,8 +36,8 @@ 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;
@@ -57,16 +47,16 @@ namespace GodotTools.Build
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)
{
- var customPropertiesList = new List<string>();
+ (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
- if (customProperties != null)
- customPropertiesList.AddRange(customProperties);
+ if (msbuildPath == null)
+ throw new FileNotFoundException("Cannot find the MSBuild executable.");
- string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList);
+ string compilerArgs = BuildArguments(buildTool, buildInfo);
- var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs);
+ var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
@@ -90,7 +80,7 @@ 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};
process.Start();
@@ -105,19 +95,7 @@ namespace GodotTools.Build
public static int Build(BuildInfo buildInfo)
{
- 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))
{
process.WaitForExit();
@@ -125,9 +103,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)
{
- using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ using (var process = LaunchBuild(buildInfo))
{
await process.WaitForExitAsync();
@@ -135,12 +113,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
+
+ 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 customProperties)
+ foreach (string customProperty in buildInfo.CustomProperties)
{
arguments += " /p:" + customProperty;
}
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/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index af8d070cbd..f36e581a5f 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -17,75 +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 dotnet CLI executable. 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 BuildManager.BuildTool.JetBrainsMsBuild:
+ 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;
- return Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe");
+
+ 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 = OS.PathWhich("dotnet");
+ if (!string.IsNullOrEmpty(dotnetCliPath))
+ return (dotnetCliPath, BuildTool.DotnetCli);
+ GD.PushError("Cannot find dotnet CLI executable. 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");
}
}
@@ -114,12 +135,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)
@@ -143,7 +164,7 @@ namespace GodotTools.Build
string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)");
vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
- 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,
@@ -171,7 +192,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/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
index 70bd552f2f..ab090c46e7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
@@ -10,7 +10,9 @@ namespace GodotTools
public sealed class BuildInfo : Reference // TODO Remove Reference 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}");
@@ -38,10 +40,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/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
index 520e665595..6399991b84 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -6,6 +6,7 @@ using GodotTools.Build;
using GodotTools.Ides.Rider;
using GodotTools.Internals;
using GodotTools.Utils;
+using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
@@ -15,20 +16,14 @@ namespace GodotTools
{
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 PropNameMsbuildJetBrains = "MSBuild (JetBrains Rider)";
+ 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 enum BuildTool
- {
- MsBuildMono,
- MsBuildVs,
- JetBrainsMsBuild
- }
-
private static void RemoveOldIssuesFile(BuildInfo buildInfo)
{
var issuesFile = GetIssuesFilePath(buildInfo);
@@ -158,7 +153,7 @@ namespace GodotTools
}
}
- public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
+ public static bool BuildProjectBlocking(string config, [CanBeNull] string platform = null)
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
@@ -174,27 +169,18 @@ namespace GodotTools
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.MsBuildMono ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Build"}, config, restore: true);
- foreach (var godotDefine in godotDefines)
- constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
+ // 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())
- constants += "GODOT_REAL_T_IS_DOUBLE;";
-
- constants += buildTool != BuildTool.MsBuildMono ? "\"" : "\\\"";
-
- buildInfo.CustomProperties.Add(constants);
+ buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
if (!Build(buildInfo))
{
@@ -219,7 +205,7 @@ namespace GodotTools
if (File.Exists(editorScriptsMetadataPath))
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
- var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest;
+ var currentPlayRequest = GodotSharpEditor.Instance.CurrentPlaySettings;
if (currentPlayRequest != null)
{
@@ -233,38 +219,55 @@ namespace GodotTools
",server=n");
}
- return true; // Requested play from an external editor/IDE which already built the project
+ if (!currentPlayRequest.Value.BuildBeforePlaying)
+ 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("Debug", godotDefines);
+ return BuildProjectBlocking("Debug");
}
public static void Initialize()
{
// Build tool settings
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var msbuild = BuildTool.MsBuildMono;
+
+ BuildTool msbuildDefault;
+
if (OS.IsWindows)
- msbuild = RiderPathManager.IsExternalEditorSetToRider(editorSettings)
- ? BuildTool.JetBrainsMsBuild
- : BuildTool.MsBuildVs;
+ {
+ 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;
- EditorDef("mono/builds/build_tool", msbuild);
+ 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"] = OS.IsWindows ?
- $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameMsbuildJetBrains}" :
- $"{PropNameMsbuildMono}"
+ ["hint_string"] = hintString
});
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
index 938c3d8be1..8596cd24af 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
@@ -72,7 +72,7 @@ namespace GodotTools
{
string[] csvColumns = file.GetCsvLine();
- if (csvColumns.Length == 1 && csvColumns[0].Empty())
+ if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0]))
return;
if (csvColumns.Length != 7)
@@ -113,14 +113,14 @@ namespace GodotTools
throw new IndexOutOfRangeException("Item list index out of range");
// Get correct issue idx from issue list
- int issueIndex = (int)issuesList.GetItemMetadata(idx);
+ int issueIndex = (int)(long)issuesList.GetItemMetadata(idx);
- if (idx < 0 || idx >= issues.Count)
+ if (issueIndex < 0 || issueIndex >= issues.Count)
throw new IndexOutOfRangeException("Issue index out of range");
BuildIssue issue = issues[issueIndex];
- if (issue.ProjectFile.Empty() && issue.File.Empty())
+ if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
return;
string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
@@ -158,14 +158,14 @@ namespace GodotTools
string tooltip = string.Empty;
tooltip += $"Message: {issue.Message}";
- if (!issue.Code.Empty())
+ if (!string.IsNullOrEmpty(issue.Code))
tooltip += $"\nCode: {issue.Code}";
tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
string text = string.Empty;
- if (!issue.File.Empty())
+ if (!string.IsNullOrEmpty(issue.File))
{
text += $"{issue.File}({issue.Line},{issue.Column}): ";
@@ -174,7 +174,7 @@ namespace GodotTools
tooltip += $"\nColumn: {issue.Column}";
}
- if (!issue.ProjectFile.Empty())
+ if (!string.IsNullOrEmpty(issue.ProjectFile))
tooltip += $"\nProject: {issue.ProjectFile}";
text += issue.Message;
diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
index 421729cc11..a8afb38728 100644
--- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
@@ -1,9 +1,9 @@
using Godot;
using System;
+using System.Linq;
using Godot.Collections;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
-using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
using Directory = GodotTools.Utils.Directory;
@@ -15,7 +15,7 @@ namespace GodotTools
{
try
{
- return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { });
+ return ProjectGenerator.GenAndSaveGameProject(dir, name);
}
catch (Exception e)
{
@@ -24,14 +24,6 @@ namespace GodotTools
}
}
- 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);
- }
-
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static ulong ConvertToTimestamp(this DateTime value)
@@ -40,81 +32,77 @@ namespace GodotTools
return (ulong)elapsedTime.TotalSeconds;
}
- public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+ private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata)
{
- if (File.Exists(outputPath))
- File.Delete(outputPath);
+ fileMetadata = null;
- var oldDict = Internal.GetScriptsMetadataOrNothing();
- var newDict = new Godot.Collections.Dictionary<string, object>();
+ var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr);
- foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
+ if (parseError != Error.Ok)
{
- string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
+ GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}");
+ return false;
+ }
- ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
+ string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile);
- 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;
- }
- }
- }
+ var firstMatch = classes.FirstOrDefault(classDecl =>
+ classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object.
+ classDecl.SearchName != searchName // Filter by the name we're looking for
+ );
+
+ if (firstMatch == null)
+ return false; // Not found
- Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr);
- if (parseError != Error.Ok)
+ fileMetadata = new Dictionary
+ {
+ ["modified_time"] = $"{modifiedTime}",
+ ["class"] = new Dictionary
{
- GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}");
- continue;
+ ["namespace"] = firstMatch.Namespace,
+ ["class_name"] = firstMatch.Name,
+ ["nested"] = firstMatch.Nested
}
+ };
- string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
-
- var classDict = new Dictionary();
+ return true;
+ }
- foreach (var classDecl in classes)
- {
- if (classDecl.BaseCount == 0)
- continue; // Does not inherit nor implement anything, so it can't be a script class
+ public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+ {
+ var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate();
- string classCmp = classDecl.Nested ?
- classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
- classDecl.Name;
+ bool IsUpToDate(string includeFile, ulong modifiedTime)
+ {
+ return metadataDict.TryGetValue(includeFile, out var oldFileVar) &&
+ ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string,
+ out ulong storedModifiedTime) && storedModifiedTime == modifiedTime;
+ }
- if (classCmp != searchName)
- continue;
+ var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile")
+ .Select(path => ("res://" + path).SimplifyGodotPath())
+ .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp())
+ .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value))
+ .ToArray();
- classDict["namespace"] = classDecl.Namespace;
- classDict["class_name"] = classDecl.Name;
- classDict["nested"] = classDecl.Nested;
- break;
- }
+ foreach (var pair in outdatedFiles)
+ {
+ metadataDict.Remove(pair.Key);
- if (classDict.Count == 0)
- continue; // Not found
+ string includeFile = pair.Key;
- newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict };
+ if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata))
+ metadataDict[includeFile] = fileMetadata;
}
- if (newDict.Count > 0)
- {
- string json = JSON.Print(newDict);
+ string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict);
- string baseDir = outputPath.GetBaseDir();
+ string baseDir = outputPath.GetBaseDir();
- if (!Directory.Exists(baseDir))
- Directory.CreateDirectory(baseDir);
+ if (!Directory.Exists(baseDir))
+ Directory.CreateDirectory(baseDir);
- File.WriteAllText(outputPath, json);
- }
+ File.WriteAllText(outputPath, json);
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
index f1765f7e19..f60e469503 100755
--- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
@@ -587,7 +587,7 @@ MONO_AOT_MODE_LAST = 1000,
string arch = "x86_64";
return $"{platform}-{arch}";
}
- case OS.Platforms.X11:
+ case OS.Platforms.LinuxBSD:
case OS.Platforms.Server:
{
string arch = bits == "64" ? "x86_64" : "i686";
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 2ceb4888a2..554763eecb 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using GodotTools.Core;
using GodotTools.Internals;
+using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
@@ -145,9 +146,7 @@ namespace GodotTools.Export
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 ??
@@ -160,10 +159,7 @@ namespace GodotTools.Export
AddFile(scriptsMetadataPath, scriptsMetadataPath);
- // Turn export features into defines
- var godotDefines = features;
-
- if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
+ if (!BuildManager.BuildProjectBlocking(buildConfig, platform))
throw new Exception("Failed to build project");
// Add dependency assemblies
@@ -289,6 +285,7 @@ namespace GodotTools.Export
}
}
+ [NotNull]
private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir)
{
string target = isDebug ? "release_debug" : "release";
@@ -343,18 +340,19 @@ namespace GodotTools.Export
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.iOS, OS.Platforms.HTML5 }.Contains(platform);
+ return !new[] {OS.Platforms.OSX, 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)
@@ -391,7 +389,7 @@ namespace GodotTools.Export
/// </summary>
private static bool PlatformRequiresCustomBcl(string platform)
{
- if (new[] { OS.Platforms.Android, OS.Platforms.iOS, 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.
@@ -414,7 +412,7 @@ namespace GodotTools.Export
case OS.Platforms.UWP:
return "net_4_x_win";
case OS.Platforms.OSX:
- case OS.Platforms.X11:
+ case OS.Platforms.LinuxBSD:
case OS.Platforms.Server:
case OS.Platforms.Haiku:
return "net_4_x";
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 c070cb16d9..a363ecc920 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Linq;
using GodotTools.Ides;
using GodotTools.Ides.Rider;
using GodotTools.Internals;
@@ -29,7 +30,7 @@ namespace GodotTools
private AcceptDialog aboutDialog;
private CheckBox aboutDialogCheckBox;
- private ToolButton bottomPanelBtn;
+ private Button bottomPanelBtn;
public GodotIdeManager GodotIdeManager { get; private set; }
@@ -37,6 +38,8 @@ namespace GodotTools
public BottomPanel BottomPanel { get; private set; }
+ public PlaySettings? CurrentPlaySettings { get; set; }
+
public static string ProjectAssemblyName
{
get
@@ -172,36 +175,6 @@ namespace GodotTools
// 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;
}
-
- var fileSystemDock = GetEditorInterface().GetFileSystemDock();
-
- fileSystemDock.FilesMoved += (file, newFile) =>
- {
- if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
- {
- ProjectUtils.RenameItemInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(file), ProjectSettings.GlobalizePath(newFile));
- }
- };
-
- fileSystemDock.FileRemoved += file =>
- {
- if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
- ProjectUtils.RemoveItemFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(file));
- };
-
- fileSystemDock.FolderMoved += (oldFolder, newFolder) =>
- {
- ProjectUtils.RenameItemsToNewFolderInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(oldFolder), ProjectSettings.GlobalizePath(newFolder));
- };
-
- fileSystemDock.FolderRemoved += oldFolder =>
- {
- ProjectUtils.RemoveItemsInFolderFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(oldFolder));
- };
}
}
@@ -228,15 +201,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:
@@ -249,17 +246,20 @@ namespace GodotTools
{
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);
@@ -300,7 +300,7 @@ namespace GodotTools
if (line >= 0)
{
args.Add("-g");
- args.Add($"{scriptPath}:{line + 1}:{col}");
+ args.Add($"{scriptPath}:{line}:{col}");
}
else
{
@@ -311,7 +311,7 @@ namespace GodotTools
if (OS.IsOSX)
{
- if (!osxAppBundleInstalled && _vsCodePath.Empty())
+ if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@@ -321,7 +321,7 @@ namespace GodotTools
}
else
{
- if (_vsCodePath.Empty())
+ if (string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@@ -341,7 +341,6 @@ namespace GodotTools
break;
}
-
default:
throw new ArgumentOutOfRangeException();
}
@@ -360,6 +359,37 @@ namespace GodotTools
return BuildManager.EditorBuildCallback();
}
+ 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());
+ }
+ }
+
public override void EnablePlugin()
{
base.EnablePlugin();
@@ -417,7 +447,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" +
@@ -439,36 +469,7 @@ namespace GodotTools
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
- 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 csproj from old configuration names to: Debug, ExportDebug and ExportRelease
- ProjectUtils.MigrateFromOldConfigNames(msbuildProject);
-
- // Apply the other fixes only after configurations have been migrated
-
- // Make sure the existing project has Api assembly references configured correctly
- ProjectUtils.FixApiHintPath(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());
- }
+ ApplyNecessaryChangesToSolution();
}
else
{
@@ -478,7 +479,7 @@ namespace GodotTools
menuPopup.IdPressed += _MenuOptionPressed;
- var buildButton = new ToolButton
+ var buildButton = new Button
{
Text = "Build",
HintTooltip = "Build solution",
@@ -494,7 +495,8 @@ 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}";
}
@@ -505,7 +507,7 @@ namespace GodotTools
$",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}" +
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index ac9379adf8..3f14629b11 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -1,123 +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\AotBuilder.cs" />
- <Compile Include="Export\ExportPlugin.cs" />
- <Compile Include="Export\XcodeHelper.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 ae05710f4f..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 == Node.NotificationWmFocusIn)
+ if (what == Node.NotificationWmWindowFocusIn)
{
RestartTimer();
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 54f0ffab96..e4932ca217 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.IsOSX && 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..17f3339560
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
@@ -0,0 +1,377 @@
+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(() =>
+ {
+ GodotSharpEditor.Instance.CurrentPlaySettings = new PlaySettings();
+ Internal.EditorRunPlay();
+ GodotSharpEditor.Instance.CurrentPlaySettings = null;
+ });
+ return Task.FromResult<Response>(new PlayResponse());
+ }
+
+ private static Task<Response> HandleDebugPlay(DebugPlayRequest request)
+ {
+ DispatchToMainThread(() =>
+ {
+ GodotSharpEditor.Instance.CurrentPlaySettings =
+ new PlaySettings(request.DebuggerHost, request.DebuggerPort, request.BuildBeforePlaying ?? true);
+ Internal.EditorRunPlay();
+ GodotSharpEditor.Instance.CurrentPlaySettings = null;
+ });
+ 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..d6fa2eeba7 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()
{
@@ -59,6 +61,8 @@ namespace GodotTools.Ides.MonoDevelop
if (command == null)
throw new FileNotFoundException();
+ LaunchTime = DateTime.Now;
+
if (newWindow)
{
process = Process.Start(new ProcessStartInfo
@@ -88,6 +92,12 @@ namespace GodotTools.Ides.MonoDevelop
this.editorId = editorId;
}
+ public void Dispose()
+ {
+ IsDisposed = true;
+ process?.Dispose();
+ }
+
private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames;
private static readonly IReadOnlyDictionary<EditorId, string> BundleIds;
@@ -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 e3a4fa7b45..e22e9af919 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
@@ -36,7 +36,7 @@ namespace GodotTools.Ides.Rider
{
return CollectRiderInfosMac();
}
- if (OS.IsUnixLike())
+ if (OS.IsUnixLike)
{
return CollectAllRiderPathsLinux();
}
@@ -141,16 +141,16 @@ namespace GodotTools.Ides.Rider
if (OS.IsOSX)
{
var home = Environment.GetEnvironmentVariable("HOME");
- if (string.IsNullOrEmpty(home))
+ 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))
+ if (string.IsNullOrEmpty(home))
return string.Empty;
var localAppData = Path.Combine(home, @".local/share");
return GetToolboxRiderRootPath(localAppData);
@@ -209,7 +209,7 @@ namespace GodotTools.Ides.Rider
private static string GetRelativePathToBuildTxt()
{
- if (OS.IsWindows || OS.IsUnixLike())
+ if (OS.IsWindows || OS.IsUnixLike)
return "../../build.txt";
if (OS.IsOSX)
return "Contents/Resources/build.txt";
@@ -322,7 +322,7 @@ namespace GodotTools.Ides.Rider
class SettingsJson
{
public string install_location;
-
+
[CanBeNull]
public static string GetInstallLocationFromJson(string json)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 026a7db89c..7e5049e4b7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -2,6 +2,7 @@ using System;
using System.Runtime.CompilerServices;
using Godot;
using Godot.Collections;
+using GodotTools.IdeMessaging.Requests;
namespace GodotTools.Internals
{
@@ -52,6 +53,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)]
@@ -111,6 +115,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
index 7fb087467f..c72a84c513 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
@@ -13,9 +13,13 @@ namespace GodotTools.Internals
public string Name { get; }
public string Namespace { get; }
public bool Nested { get; }
- public int BaseCount { get; }
+ public long BaseCount { get; }
- public ClassDecl(string name, string @namespace, bool nested, int baseCount)
+ public string SearchName => Nested ?
+ Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
+ Name;
+
+ public ClassDecl(string name, string @namespace, bool nested, long baseCount)
{
Name = name;
Namespace = @namespace;
@@ -45,7 +49,7 @@ namespace GodotTools.Internals
(string)classDeclDict["name"],
(string)classDeclDict["namespace"],
(bool)classDeclDict["nested"],
- (int)classDeclDict["base_count"]
+ (long)classDeclDict["base_count"]
));
}
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 b057ac12c6..6c05891f2c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -22,7 +22,10 @@ namespace GodotTools.Utils
{
public const string Windows = "Windows";
public const string OSX = "OSX";
- public const string X11 = "X11";
+ 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";
@@ -35,7 +38,7 @@ namespace GodotTools.Utils
{
public const string Windows = "windows";
public const string OSX = "osx";
- public const string X11 = "linuxbsd";
+ public const string LinuxBSD = "linuxbsd";
public const string Server = "server";
public const string UWP = "uwp";
public const string Haiku = "haiku";
@@ -48,7 +51,10 @@ namespace GodotTools.Utils
{
[Names.Windows] = Platforms.Windows,
[Names.OSX] = Platforms.OSX,
- [Names.X11] = Platforms.X11,
+ [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,
@@ -62,38 +68,39 @@ 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.OSX, 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> _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 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, Names.iOS };
-
- 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 ? ';' : ':';
@@ -121,10 +128,10 @@ 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([NotNull] string name)
@@ -189,7 +196,7 @@ namespace GodotTools.Utils
startInfo.UseShellExecute = false;
- using (var process = new Process { StartInfo = startInfo })
+ using (var process = new Process {StartInfo = startInfo})
{
process.Start();
process.WaitForExit();
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 ee92a6234a..a17c371117 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -45,7 +45,6 @@
#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 +61,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"
@@ -105,7 +102,6 @@
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()
@@ -115,7 +111,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);
@@ -125,8 +120,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 {
@@ -148,7 +144,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);
@@ -160,8 +155,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 {
@@ -183,11 +179,11 @@ 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.empty()) {
return String();
+ }
DocData *doc = EditorHelp::get_doc_data();
@@ -204,8 +200,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);
@@ -214,19 +211,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);
@@ -237,13 +237,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");
+ }
}
}
@@ -416,8 +418,9 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
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) {
@@ -454,8 +457,9 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
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) {
@@ -566,8 +570,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") {
@@ -583,8 +591,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);
@@ -603,8 +612,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.
@@ -634,15 +644,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());
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();
@@ -654,21 +664,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;
@@ -679,22 +690,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];
}
@@ -704,12 +718,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);
@@ -758,8 +772,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);
@@ -769,7 +784,6 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) {
}
void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
-
// Constants (in partial GD class)
p_output.append("\n#pragma warning disable CS1591 // Disable warning: "
@@ -805,8 +819,9 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
p_output.append(";");
}
- if (!global_constants.empty())
+ if (!global_constants.empty()) {
p_output.append("\n");
+ }
p_output.append(INDENT1 CLOSE_BLOCK); // end of GD class
@@ -868,8 +883,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
@@ -878,7 +894,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);
@@ -904,8 +919,9 @@ 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);
}
@@ -913,17 +929,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);
}
@@ -954,10 +973,12 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
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
@@ -966,8 +987,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);
@@ -986,14 +1008,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);
@@ -1016,17 +1038,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);
}
@@ -1056,10 +1081,12 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
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
@@ -1068,8 +1095,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);
@@ -1088,14 +1116,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));
@@ -1143,7 +1171,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();
@@ -1218,93 +1245,89 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append(INDENT1 "{");
- if (class_doc) {
+ // Add constants
- // Add constants
-
- for (const List<ConstantInterface>::Element *E = itype.constants.front(); E; E = E->next()) {
- 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), &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.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
@@ -1377,15 +1400,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
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 */
@@ -1399,7 +1424,6 @@ 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
@@ -1452,6 +1476,9 @@ 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_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>();
@@ -1471,8 +1498,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(" ");
@@ -1542,9 +1570,11 @@ 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);
+ ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG,
+ "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'.");
+
String method_bind_field = "__method_bind_" + itos(p_method_bind_count);
String arguments_sig;
@@ -1560,29 +1590,41 @@ 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 (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);
+ }
}
}
@@ -1600,17 +1642,19 @@ 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);
@@ -1660,14 +1704,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())
+ if (p_imethod.deprecation_message.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);
@@ -1727,8 +1776,9 @@ 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");
@@ -1755,10 +1805,14 @@ Error BindingsGenerator::_generate_cs_signal(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 signal" + p_itype.name + "." + p_isignal.name + "'.");
+
// Add the current arguments to the signature
- if (F != p_isignal.arguments.front())
+ if (F != p_isignal.arguments.front()) {
arguments_sig += ", ";
+ }
arguments_sig += arg_type->cs_type;
arguments_sig += " ";
@@ -1785,8 +1839,9 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
}
if (p_isignal.is_deprecated) {
- if (p_isignal.deprecation_message.empty())
+ if (p_isignal.deprecation_message.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);
@@ -1817,8 +1872,9 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
// Generate event
p_output.append(MEMBER_BEGIN "[Signal]" MEMBER_BEGIN "public ");
- if (p_itype.is_singleton)
+ if (p_itype.is_singleton) {
p_output.append("static ");
+ }
p_output.append("event ");
p_output.append(delegate_name);
@@ -1826,18 +1882,20 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append(p_isignal.proxy_name);
p_output.append("\n" OPEN_BLOCK_L2);
- if (p_itype.is_singleton)
+ if (p_itype.is_singleton) {
p_output.append("add => Singleton.Connect(__signal_name_");
- else
+ } 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)
+ if (p_itype.is_singleton) {
p_output.append(INDENT3 "remove => Singleton.Disconnect(__signal_name_");
- else
+ } else {
p_output.append(INDENT3 "remove => Disconnect(__signal_name_");
+ }
p_output.append(p_isignal.name);
p_output.append(", new Callable(value));\n");
@@ -1848,7 +1906,6 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
}
Error BindingsGenerator::generate_glue(const String &p_output_dir) {
-
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
bool dir_exists = DirAccess::exists(p_output_dir);
@@ -1872,7 +1929,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);
}
@@ -1893,8 +1949,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);
@@ -1906,8 +1963,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);
@@ -1953,7 +2011,6 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
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;
@@ -2007,8 +2064,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");
@@ -2020,7 +2078,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 + "'.");
@@ -2032,9 +2089,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;
@@ -2062,10 +2119,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);
}
@@ -2095,8 +2154,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
@@ -2135,7 +2195,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
if (return_type->ret_as_byref_arg) {
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);
@@ -2214,30 +2274,33 @@ 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);
@@ -2249,18 +2312,19 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con
}
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_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);
@@ -2269,7 +2333,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";
@@ -2302,7 +2365,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";
@@ -2320,8 +2382,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::TRANSFORM:
+ case Variant::TRANSFORM2D:
+ case Variant::BASIS:
+ case Variant::QUAT:
+ 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;
@@ -2383,22 +2522,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_SUBGROUP || 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));
@@ -2410,8 +2557,6 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
iprop.proxy_name += "_";
}
- iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash...
-
iprop.prop_doc = nullptr;
for (int i = 0; i < itype.class_doc->properties.size(); i++) {
@@ -2440,20 +2585,23 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
int argc = method_info.arguments.size();
- if (method_info.name.empty())
+ if (method_info.name.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;
@@ -2475,9 +2623,8 @@ 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_PRINT("Notification: New unexpected virtual non-overridable method found."
" We only expected Object.free, but found '" +
@@ -2488,13 +2635,12 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
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_PRINT("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_Reference);
+ 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) {
@@ -2585,6 +2731,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();
@@ -2764,8 +2914,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:
@@ -2798,8 +2948,9 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
}
break;
case Variant::TRANSFORM:
- if (p_val.operator Transform() == Transform())
+ if (p_val.operator Transform() == Transform()) {
r_iarg.default_argument.clear();
+ }
r_iarg.default_argument = "new %s(" + r_iarg.default_argument + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
@@ -2865,14 +3016,14 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
break;
}
- 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;
@@ -3248,7 +3399,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
}
void BindingsGenerator::_populate_global_constants() {
-
int global_constants_count = GlobalConstants::get_global_constant_count();
if (global_constants_count > 0) {
@@ -3259,7 +3409,6 @@ 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 = GlobalConstants::get_global_constant_name(i);
const DocData::ConstantDoc *const_doc = nullptr;
@@ -3338,14 +3487,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;
@@ -3356,7 +3503,6 @@ void BindingsGenerator::_log(const char *p_format, ...) {
}
void BindingsGenerator::_initialize() {
-
initialized = false;
EditorHelp::generate_doc();
@@ -3377,14 +3523,14 @@ 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) {
-
const int NUM_OPTIONS = 2;
String generate_all_glue_option = "--generate-mono-glue";
String generate_cs_glue_option = "--generate-mono-cs-glue";
@@ -3447,21 +3593,25 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
}
if (glue_dir_path.length()) {
- if (bindings_generator.generate_glue(glue_dir_path) != OK)
+ 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)
+ 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)
+ 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)
+ if (bindings_generator.generate_glue(cpp_dir_path) != OK) {
ERR_PRINT(generate_cpp_glue_option + ": Failed to generate the C++ glue.");
+ }
}
// Exit once done
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 7c87c688db..90c1c9f3ee 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -41,7 +41,6 @@
#include "core/ustring.h"
class BindingsGenerator {
-
struct ConstantInterface {
String name;
String proxy_name;
@@ -85,16 +84,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,7 +102,9 @@ class BindingsGenerator {
TypeReference type;
String name;
- DefaultParamMode def_param_mode;
+
+ Variant def_param_value;
+ DefaultParamMode def_param_mode = CONSTANT;
/**
* Determines the expression for the parameter default value.
@@ -116,9 +113,7 @@ class BindingsGenerator {
*/
String default_argument;
- ArgumentInterface() {
- def_param_mode = CONSTANT;
- }
+ ArgumentInterface() {}
};
struct MethodInterface {
@@ -138,19 +133,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).
@@ -158,27 +153,20 @@ 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 = nullptr;
- is_deprecated = false;
- }
+ MethodInterface() {}
};
struct SignalInterface {
@@ -192,19 +180,16 @@ class BindingsGenerator {
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);
}
- SignalInterface() {
- method_doc = nullptr;
- is_deprecated = false;
- }
+ SignalInterface() {}
};
struct TypeInterface {
@@ -225,26 +210,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_reference = 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.
*/
- 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
@@ -252,7 +237,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
@@ -279,7 +264,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.
@@ -362,7 +347,7 @@ class BindingsGenerator {
*/
String im_type_out;
- const DocData::ClassDoc *class_doc;
+ const DocData::ClassDoc *class_doc = nullptr;
List<ConstantInterface> constants;
List<EnumInterface> enums;
@@ -372,8 +357,9 @@ class BindingsGenerator {
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 nullptr;
@@ -381,8 +367,9 @@ class BindingsGenerator {
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 nullptr;
@@ -390,8 +377,9 @@ class BindingsGenerator {
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;
@@ -399,8 +387,9 @@ class BindingsGenerator {
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)
+ if (E->get().proxy_name == p_proxy_name) {
return &E->get();
+ }
}
return nullptr;
@@ -482,24 +471,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 = nullptr;
- }
+ TypeInterface() {}
};
struct InternalCall {
@@ -532,8 +504,8 @@ class BindingsGenerator {
}
};
- bool log_print_enabled;
- bool initialized;
+ bool log_print_enabled = true;
+ bool initialized = false;
OrderedHashMap<StringName, TypeInterface> obj_types;
@@ -557,58 +529,70 @@ class BindingsGenerator {
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_StringName;
- StringName type_NodePath;
- 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_StringName = StaticCString::create("StringName");
- type_NodePath = StaticCString::create("NodePath");
- 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_Reference = StaticCString::create("Reference");
+ 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 &);
@@ -619,7 +603,9 @@ 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 nullptr;
@@ -627,20 +613,22 @@ class BindingsGenerator {
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 nullptr;
}
inline String get_unique_sig(const TypeInterface &p_type) {
- if (p_type.is_reference)
+ if (p_type.is_reference) {
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;
}
@@ -659,6 +647,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();
@@ -696,9 +685,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..942c6d26a6
--- /dev/null
+++ b/modules/mono/editor/code_completion.cpp
@@ -0,0 +1,250 @@
+/*************************************************************************/
+/* code_completion.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 "code_completion.h"
+
+#include "core/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.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_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/editor/csharp_project.h b/modules/mono/editor/code_completion.h
index 515b8d3d62..77673b766f 100644
--- a/modules/mono/editor/csharp_project.h
+++ b/modules/mono/editor/code_completion.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* csharp_project.h */
+/* code_completion.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,15 +28,29 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef CSHARP_PROJECT_H
-#define CSHARP_PROJECT_H
+#ifndef CODE_COMPLETION_H
+#define CODE_COMPLETION_H
#include "core/ustring.h"
+#include "core/variant.h"
-namespace CSharpProject {
+namespace gdmono {
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
+enum class CompletionKind {
+ INPUT_ACTIONS = 0,
+ NODE_PATHS,
+ RESOURCE_PATHS,
+ SCENE_PATHS,
+ SHADER_PARAMS,
+ SIGNALS,
+ THEME_COLORS,
+ THEME_CONSTANTS,
+ THEME_FONTS,
+ THEME_STYLES
+};
-} // namespace CSharpProject
+PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file);
-#endif // CSHARP_PROJECT_H
+} // namespace gdmono
+
+#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 e5c2d023d3..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 = nullptr;
- klass->get_method("AddItemToProjectChecked", 3)->invoke(nullptr, 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 c3e7e67ae9..68fc372959 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -48,6 +48,7 @@
#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"
@@ -323,7 +324,7 @@ MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType
MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype);
- uint32_t type_encoding = mono_type_get_type(dict_type);
+ int 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);
@@ -354,6 +355,12 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
}
}
+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,7 +399,6 @@ 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);
@@ -454,6 +460,7 @@ void register_editor_internal_calls() {
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);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", (void *)godot_icall_Internal_CodeCompletionRequest);
// Globals
mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale);
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index 4126da16be..b15e9b060a 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -32,68 +32,71 @@
#include <mono/metadata/image.h>
+#include "core/io/file_access_pack.h"
#include "core/os/os.h"
+#include "core/project_settings.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) {
+struct AssemblyRefInfo {
+ String name;
+ uint16_t major;
+ uint16_t minor;
+ uint16_t build;
+ uint16_t revision;
+};
+
+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_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);
+
+ const String &ref_name = ref_info.name;
- if (r_assembly_dependencies.has(ref_name))
+ if (r_assembly_dependencies.has(ref_name)) {
continue;
+ }
GDMonoAssembly *ref_assembly = nullptr;
- 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 != nullptr)
- 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 != nullptr)
- 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 != nullptr)
- break;
- }
- }
- }
- ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ {
+ MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A"
+ CRASH_COND(ref_aname == nullptr);
+ SCOPE_EXIT {
+ mono_assembly_name_free(ref_aname);
+ mono_free(ref_aname);
+ };
+
+ mono_assembly_get_assemblyref(image, i, ref_aname);
- // Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir.
- r_assembly_dependencies[ref_name] = path;
+ if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
+ ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ }
+
+ r_assembly_dependencies[ref_name] = ref_assembly->get_path();
+ }
Error err = get_assembly_dependencies(ref_assembly, 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 + "'.");
@@ -113,6 +116,11 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
+ 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_assemblies[*key];
@@ -123,8 +131,9 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
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_assembly_dependencies);
- if (err != OK)
+ if (err != OK) {
return err;
+ }
}
return OK;
diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp
index bece23c9a6..430c82953e 100644
--- a/modules/mono/editor/script_class_parser.cpp
+++ b/modules/mono/editor/script_class_parser.cpp
@@ -54,13 +54,11 @@ const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = {
};
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': {
@@ -181,14 +179,24 @@ ScriptClassParser::Token ScriptClassParser::get_token() {
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 '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;
case '\\':
res = '\\';
break;
@@ -200,8 +208,9 @@ ScriptClassParser::Token ScriptClassParser::get_token() {
tk_string += res;
} else {
- if (code[idx] == '\n')
+ if (code[idx] == '\n') {
line++;
+ }
tk_string += code[idx];
}
idx++;
@@ -226,7 +235,7 @@ ScriptClassParser::Token ScriptClassParser::get_token() {
if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
//a number
const CharType *rptr;
- double number = String::to_double(&code[idx], &rptr);
+ double number = String::to_float(&code[idx], &rptr);
idx += (rptr - &code[idx]);
value = number;
return TK_NUMBER;
@@ -258,7 +267,6 @@ ScriptClassParser::Token ScriptClassParser::get_token() {
}
Error ScriptClassParser::_skip_generic_type_params() {
-
Token tk;
while (true) {
@@ -293,15 +301,17 @@ Error ScriptClassParser::_skip_generic_type_params() {
tk = get_token();
- if (tk != TK_PERIOD)
+ if (tk != TK_PERIOD) {
break;
+ }
}
}
if (tk == TK_OP_LESS) {
Error err = _skip_generic_type_params();
- if (err)
+ if (err) {
return err;
+ }
tk = get_token();
}
@@ -327,7 +337,6 @@ Error ScriptClassParser::_skip_generic_type_params() {
}
Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {
-
Token tk = get_token();
if (tk != TK_IDENTIFIER) {
@@ -343,12 +352,14 @@ Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {
// 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)
+ if (err) {
return err;
+ }
}
- if (code[idx] != '.') // We only want to take the next token if it's a period
+ if (code[idx] != '.') { // We only want to take the next token if it's a period
return OK;
+ }
tk = get_token();
@@ -360,19 +371,20 @@ Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {
}
Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) {
-
String name;
Error err = _parse_type_full_name(name);
- if (err)
+ if (err) {
return err;
+ }
Token tk = get_token();
if (tk == TK_COMMA) {
err = _parse_class_base(r_base);
- if (err)
+ if (err) {
return err;
+ }
} else if (tk == TK_IDENTIFIER && String(value) == "where") {
err = _parse_type_constraints();
if (err) {
@@ -428,8 +440,9 @@ Error ScriptClassParser::_parse_type_constraints() {
tk = get_token();
- if (tk != TK_PERIOD)
+ if (tk != TK_PERIOD) {
break;
+ }
}
}
}
@@ -447,8 +460,9 @@ Error ScriptClassParser::_parse_type_constraints() {
}
} else if (tk == TK_OP_LESS) {
Error err = _skip_generic_type_params();
- if (err)
+ if (err) {
return err;
+ }
} else if (tk == TK_CURLY_BRACKET_OPEN) {
return OK;
} else {
@@ -460,7 +474,6 @@ Error ScriptClassParser::_parse_type_constraints() {
}
Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) {
-
Token tk = get_token();
if (tk == TK_IDENTIFIER) {
@@ -487,7 +500,6 @@ Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stac
}
Error ScriptClassParser::parse(const String &p_code) {
-
code = p_code;
idx = 0;
line = 0;
@@ -519,8 +531,9 @@ Error ScriptClassParser::parse(const String &p_code) {
const NameDecl &name_decl = E->value();
if (name_decl.type == NameDecl::NAMESPACE_DECL) {
- if (E != name_stack.front())
+ if (E != name_stack.front()) {
class_decl.namespace_ += ".";
+ }
class_decl.namespace_ += name_decl.name;
} else {
class_decl.name += name_decl.name + ".";
@@ -537,8 +550,9 @@ Error ScriptClassParser::parse(const String &p_code) {
if (tk == TK_COLON) {
Error err = _parse_class_base(class_decl.base);
- if (err)
+ if (err) {
return err;
+ }
curly_stack++;
type_curly_stack++;
@@ -552,8 +566,9 @@ Error ScriptClassParser::parse(const String &p_code) {
generic = true;
Error err = _skip_generic_type_params();
- if (err)
+ if (err) {
return err;
+ }
} else if (tk == TK_IDENTIFIER && String(value) == "where") {
Error err = _parse_type_constraints();
if (err) {
@@ -581,8 +596,9 @@ Error ScriptClassParser::parse(const String &p_code) {
classes.push_back(class_decl);
} else if (OS::get_singleton()->is_stdout_verbose()) {
String full_name = class_decl.namespace_;
- if (full_name.length())
+ 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());
}
@@ -599,8 +615,9 @@ Error ScriptClassParser::parse(const String &p_code) {
int at_level = curly_stack;
Error err = _parse_namespace_name(name, curly_stack);
- if (err)
+ if (err) {
return err;
+ }
NameDecl name_decl;
name_decl.name = name;
@@ -611,8 +628,9 @@ Error ScriptClassParser::parse(const String &p_code) {
} else if (tk == TK_CURLY_BRACKET_CLOSE) {
curly_stack--;
if (name_stack.has(curly_stack)) {
- if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL)
+ if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL) {
type_curly_stack--;
+ }
name_stack.erase(curly_stack);
}
}
@@ -625,8 +643,9 @@ Error ScriptClassParser::parse(const String &p_code) {
error = true;
}
- if (error)
+ if (error) {
return ERR_PARSE_ERROR;
+ }
return OK;
}
@@ -643,7 +662,6 @@ static String get_preprocessor_directive(const String &p_line, int 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());
@@ -700,8 +718,9 @@ static void run_dummy_preprocessor(String &r_source, const String &p_filepath) {
// Custom join ignoring lines removed by the preprocessor
for (int i = 0; i < lines.size(); i++) {
- if (i > 0 && include_lines[i - 1])
+ if (i > 0 && include_lines[i - 1]) {
r_source += '\n';
+ }
if (include_lines[i]) {
r_source += lines[i];
@@ -710,7 +729,6 @@ static void run_dummy_preprocessor(String &r_source, const String &p_filepath) {
}
Error ScriptClassParser::parse_file(const String &p_filepath) {
-
String source;
Error ferr = read_all_file_utf8(p_filepath, source);
diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h
index a76a3a50a9..d611e8fb74 100644
--- a/modules/mono/editor/script_class_parser.h
+++ b/modules/mono/editor/script_class_parser.h
@@ -36,7 +36,6 @@
#include "core/vector.h"
class ScriptClassParser {
-
public:
struct NameDecl {
enum Type {
@@ -54,7 +53,6 @@ public:
String namespace_;
Vector<String> base;
bool nested;
- bool has_script_attr;
};
private:
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs
index 6a4f785551..3aecce50f5 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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index baf470a0cc..5aba31c622 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,11 +135,18 @@ 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;
+ }
}
/// <summary>
@@ -157,8 +213,9 @@ namespace Godot
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();
@@ -182,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];
@@ -191,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.Quat()"/> method instead, which
+ /// returns a <see cref="Godot.Quat"/> quaternion instead of Euler angles.
+ /// </summary>
+ /// <returns>A Vector3 representing the basis rotation in Euler angles.</returns>
public Vector3 GetEuler()
{
Basis m = Orthonormalized();
@@ -223,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)
@@ -238,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)
@@ -256,16 +344,16 @@ namespace Godot
}
}
- public Vector3 GetColumn(int index)
- {
- return this[index];
- }
-
- public void SetColumn(int index, Vector3 value)
- {
- this[index] = value;
- }
-
+ /// <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;
@@ -279,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;
@@ -294,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];
@@ -309,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;
@@ -328,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);
@@ -343,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);
+ Quat from = new Quat(this);
+ Quat to = new Quat(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;
@@ -404,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
@@ -414,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
@@ -424,6 +583,12 @@ namespace Godot
);
}
+ /// <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.Quat"/> representing the basis's rotation.</returns>
public Quat Quat()
{
real_t trace = Row0[0] + Row1[1] + Row2[2];
@@ -508,11 +673,33 @@ 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; } }
+ /// <summary>
+ /// Constructs a pure rotation basis matrix from the given quaternion.
+ /// </summary>
+ /// <param name="quat">The quaternion to create the basis from.</param>
public Basis(Quat quat)
{
real_t s = 2.0f / quat.LengthSquared;
@@ -535,26 +722,41 @@ namespace Godot
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(Quat)"/> constructor instead, which
+ /// uses a <see cref="Godot.Quat"/> 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);
@@ -582,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);
@@ -637,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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
index 1d1a49945f..d851abc6d3 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,21 +115,31 @@ 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;
}
@@ -94,6 +149,10 @@ namespace Godot
}
}
+ /// <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,7 +162,7 @@ namespace Godot
float delta = max - min;
- return max != 0 ? delta / max : 0;
+ return max == 0 ? 0 : delta / max;
}
set
{
@@ -111,6 +170,10 @@ namespace Godot
}
}
+ /// <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
@@ -123,25 +186,10 @@ namespace Godot
}
}
- 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}");
- }
-
- 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,6 +256,10 @@ namespace Godot
return res;
}
+ /// <summary>
+ /// Returns the most contrasting color.
+ /// </summary>
+ /// <returns>The most contrasting color</returns>
public Color Contrasted()
{
return new Color(
@@ -278,6 +270,12 @@ namespace Godot
);
}
+ /// <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 +285,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 +299,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,18 +314,48 @@ namespace Godot
return res;
}
- public Color LinearInterpolate(Color c, float t)
+ /// <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)
{
- 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);
+ 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)
+ );
}
+ /// <summary>
+ /// Returns the color's 32-bit integer in ABGR format
+ /// (each byte represents a component of the ABGR profile).
+ /// ABGR is the reversed version of the default format.
+ /// </summary>
+ /// <returns>A uint representing this color in ABGR32 format.</returns>
public uint ToAbgr32()
{
uint c = (byte)Math.Round(a * 255);
@@ -331,6 +369,12 @@ namespace Godot
return c;
}
+ /// <summary>
+ /// Returns the color's 64-bit integer in ABGR format
+ /// (each word represents a component of the ABGR profile).
+ /// ABGR is the reversed version of the default format.
+ /// </summary>
+ /// <returns>A ulong representing this color in ABGR64 format.</returns>
public ulong ToAbgr64()
{
ulong c = (ushort)Math.Round(a * 65535);
@@ -344,6 +388,12 @@ namespace Godot
return c;
}
+ /// <summary>
+ /// Returns the color's 32-bit integer in ARGB format
+ /// (each byte represents a component of the ARGB profile).
+ /// 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()
{
uint c = (byte)Math.Round(a * 255);
@@ -357,6 +407,12 @@ namespace Godot
return c;
}
+ /// <summary>
+ /// Returns the color's 64-bit integer in ARGB format
+ /// (each word represents a component of the ARGB profile).
+ /// 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()
{
ulong c = (ushort)Math.Round(a * 65535);
@@ -370,6 +426,12 @@ namespace Godot
return c;
}
+ /// <summary>
+ /// Returns the color's 32-bit integer in RGBA format
+ /// (each byte represents a component of the RGBA profile).
+ /// RGBA is Godot's default and recommended format.
+ /// </summary>
+ /// <returns>A uint representing this color in RGBA32 format.</returns>
public uint ToRgba32()
{
uint c = (byte)Math.Round(r * 255);
@@ -383,6 +445,12 @@ namespace Godot
return c;
}
+ /// <summary>
+ /// Returns the color's 64-bit integer in RGBA format
+ /// (each word represents a component of the RGBA profile).
+ /// RGBA is Godot's default and recommended format.
+ /// </summary>
+ /// <returns>A ulong representing this color in RGBA64 format.</returns>
public ulong ToRgba64()
{
ulong c = (ushort)Math.Round(r * 65535);
@@ -396,6 +464,11 @@ namespace Godot
return c;
}
+ /// <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 +478,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 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,6 +500,24 @@ namespace Godot
this.a = a;
}
+ /// <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 a 32-bit integer
+ /// (each byte represents a component of the RGBA profile).
+ /// </summary>
+ /// <param name="rgba">The uint representing the color.</param>
public Color(uint rgba)
{
a = (rgba & 0xFF) / 255.0f;
@@ -430,6 +529,11 @@ namespace Godot
r = (rgba & 0xFF) / 255.0f;
}
+ /// <summary>
+ /// Constructs a color from a 64-bit integer
+ /// (each word represents a component of the RGBA profile).
+ /// </summary>
+ /// <param name="rgba">The ulong representing the color.</param>
public Color(ulong rgba)
{
a = (rgba & 0xFFFF) / 65535.0f;
@@ -441,6 +545,212 @@ namespace Godot
r = (rgba & 0xFFFF) / 65535.0f;
}
+ /// <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>
+ public Color(string rgba)
+ {
+ if (rgba.Length == 0)
+ {
+ r = 0f;
+ g = 0f;
+ b = 0f;
+ a = 1.0f;
+ return;
+ }
+
+ if (rgba[0] == '#')
+ {
+ rgba = rgba.Substring(1);
+ }
+
+ bool alpha;
+
+ if (rgba.Length == 8)
+ {
+ alpha = true;
+ }
+ else if (rgba.Length == 6)
+ {
+ 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)
+ {
+ a = ParseCol8(rgba, 6) / 255f;
+
+ if (a < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
+ }
+ }
+ else
+ {
+ a = 1.0f;
+ }
+
+ int from = alpha ? 2 : 0;
+
+ r = ParseCol8(rgba, 0) / 255f;
+
+ if (r < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba);
+ }
+
+ g = ParseCol8(rgba, 2) / 255f;
+
+ if (g < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba);
+ }
+
+ b = ParseCol8(rgba, 4) / 255f;
+
+ if (b < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba);
+ }
+ }
+
+ /// <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);
+ }
+
+ /// <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>
+ /// <param name="alpha">The alpha (transparency) component represented on the range of 0 to 1. Default: 1.</param>
+ /// <returns>The constructed color.</returns>
+ 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}");
+ }
+
+ Color color = Colors.namedColors[name];
+ color.a = alpha;
+ return color;
+ }
+
+ /// <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)
+ {
+ // 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>
+ /// 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)
+ {
+ 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;
+ }
+
private static int ParseCol8(string str, int ofs)
{
int ig = 0;
@@ -470,9 +780,13 @@ namespace Godot
}
if (i == 0)
+ {
ig += v * 16;
+ }
else
+ {
ig += v;
+ }
}
return ig;
@@ -490,9 +804,13 @@ namespace Godot
int lv = v & 0xF;
if (lv < 10)
+ {
c = (char)('0' + lv);
+ }
else
+ {
c = (char)('a' + lv - 10);
+ }
v >>= 4;
ret = c + ret;
@@ -504,10 +822,14 @@ namespace Godot
internal static bool HtmlIsValid(string color)
{
if (color.Length == 0)
+ {
return false;
+ }
if (color[0] == '#')
+ {
color = color.Substring(1, color.Length - 1);
+ }
bool alpha;
@@ -526,83 +848,27 @@ namespace Godot
if (alpha)
{
if (ParseCol8(color, 0) < 0)
+ {
return false;
+ }
}
int from = alpha ? 2 : 0;
if (ParseCol8(color, from + 0) < 0)
- return false;
- if (ParseCol8(color, from + 2) < 0)
- return false;
- if (ParseCol8(color, from + 4) < 0)
- return false;
-
- return true;
- }
-
- 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)
- {
- if (rgba.Length == 0)
{
- r = 0f;
- g = 0f;
- b = 0f;
- a = 1.0f;
- return;
- }
-
- if (rgba[0] == '#')
- rgba = rgba.Substring(1);
-
- bool alpha;
-
- if (rgba.Length == 8)
- {
- alpha = true;
- }
- else if (rgba.Length == 6)
- {
- alpha = false;
- }
- else
- {
- throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba);
+ return false;
}
-
- if (alpha)
+ if (ParseCol8(color, from + 2) < 0)
{
- a = ParseCol8(rgba, 0) / 255f;
-
- if (a < 0)
- throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
+ return false;
}
- else
+ if (ParseCol8(color, from + 4) < 0)
{
- a = 1.0f;
+ return false;
}
- int from = alpha ? 2 : 0;
-
- r = ParseCol8(rgba, from + 0) / 255f;
-
- if (r < 0)
- throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba);
-
- g = ParseCol8(rgba, from + 2) / 255f;
-
- if (g < 0)
- throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba);
-
- b = ParseCol8(rgba, from + 4) / 255f;
-
- 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 +956,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 +973,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 +998,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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
index f41f5e9fc8..d05a0414aa 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
@@ -3,6 +3,10 @@ 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
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 9384da0e48..e050d1fdd1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
@@ -84,7 +84,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()
@@ -94,22 +94,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()
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
index 4f7aa99df8..6eecc262d6 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);
}
+ /// <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);
}
+ /// <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);
}
+ /// <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;
}
+ /// <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 stepify.</param>
+ /// <param name="step">The step size to snap to.</param>
+ /// <returns></returns>
public static real_t Stepify(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/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
index 885845e3a4..2f8b5f297c 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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
index bbc617ea6e..b33490f9cb 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
@@ -8,15 +8,51 @@ using real_t = System.Single;
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 Quat 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 Quat : IEquatable<Quat>
{
+ /// <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
@@ -57,16 +93,35 @@ namespace Godot
}
}
+ /// <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>
+ /// 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="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The interpolated quaternion.</returns>
public Quat CubicSlerp(Quat b, Quat preA, Quat postB, real_t t)
{
real_t t2 = (1.0f - t) * t * 2f;
@@ -75,85 +130,131 @@ namespace Godot
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(Quat 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("Quat 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 Quat Inverse()
{
#if DEBUG
if (!IsNormalized())
+ {
throw new InvalidOperationException("Quat is not normalized");
+ }
#endif
return new Quat(-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 Quat Normalized()
{
return this / Length;
}
- public Quat Slerp(Quat b, real_t t)
+ /// <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 Quat Slerp(Quat to, real_t weight)
{
#if DEBUG
if (!IsNormalized())
+ {
throw new InvalidOperationException("Quat is not normalized");
- if (!b.IsNormalized())
- throw new ArgumentException("Argument is not normalized", nameof(b));
+ }
+ if (!to.IsNormalized())
+ {
+ throw new ArgumentException("Argument is not normalized", nameof(to));
+ }
#endif
- // Calculate cosine
- real_t cosom = x * b.x + y * b.y + z * b.z + w * b.w;
+ // Calculate cosine.
+ real_t cosom = x * to.x + y * to.y + z * to.z + w * to.w;
var to1 = new Quat();
- // Adjust signs if necessary
+ // 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;
+ to1.x = -to.x;
+ to1.y = -to.y;
+ to1.z = -to.z;
+ to1.w = -to.w;
}
else
{
- to1.x = b.x;
- to1.y = b.y;
- to1.z = b.z;
- to1.w = b.w;
+ to1.x = to.x;
+ to1.y = to.y;
+ to1.z = to.z;
+ to1.w = to.w;
}
real_t sinom, scale0, scale1;
- // Calculate coefficients
+ // Calculate coefficients.
if (1.0 - cosom > Mathf.Epsilon)
{
- // Standard case (Slerp)
+ // 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;
+ 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 - t;
- scale1 = t;
+ // Quaternions are very close so we can do a linear interpolation.
+ scale0 = 1.0f - weight;
+ scale1 = weight;
}
- // Calculate final values
+ // Calculate final values.
return new Quat
(
scale0 * x + scale1 * to1.x,
@@ -163,9 +264,17 @@ namespace Godot
);
}
- public Quat Slerpni(Quat b, real_t t)
+ /// <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 Quat Slerpni(Quat to, real_t weight)
{
- real_t dot = Dot(b);
+ real_t dot = Dot(to);
if (Mathf.Abs(dot) > 0.9999f)
{
@@ -174,33 +283,54 @@ namespace Godot
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;
+ real_t newFactor = Mathf.Sin(weight * theta) * sinT;
+ real_t invFactor = Mathf.Sin((1.0f - weight) * 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
+ 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("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
+ // Constants
+ private static readonly Quat _identity = new Quat(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 Quat(0, 0, 0, 1)`.</value>
+ public static Quat 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 Quat(real_t x, real_t y, real_t z, real_t w)
{
this.x = x;
@@ -209,21 +339,31 @@ namespace Godot
this.w = w;
}
- public bool IsNormalized()
- {
- return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon;
- }
-
+ /// <summary>
+ /// Constructs a quaternion from the given quaternion.
+ /// </summary>
+ /// <param name="q">The existing quaternion.</param>
public Quat(Quat q)
{
this = q;
}
+ /// <summary>
+ /// Constructs a quaternion from the given <see cref="Basis"/>.
+ /// </summary>
+ /// <param name="basis">The basis to construct from.</param>
public Quat(Basis basis)
{
this = basis.Quat();
}
+ /// <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 Quat(Vector3 eulerYXZ)
{
real_t half_a1 = eulerYXZ.y * 0.5f;
@@ -247,11 +387,19 @@ namespace Godot
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 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();
@@ -364,6 +512,12 @@ namespace Godot
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(Quat other)
{
return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index 91e614dc7b..f7703c77cc 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 rect.
+ /// </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 rect.</returns>
public Rect2 Abs()
{
Vector2 end = End;
@@ -45,12 +72,19 @@ namespace Godot
return new Rect2(topLeft, _size.Abs());
}
+ /// <summary>
+ /// Returns the intersection of this Rect2 and `b`.
+ /// </summary>
+ /// <param name="b">The other rect.</param>
+ /// <returns>The clipped rect.</returns>
public Rect2 Clip(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 +98,11 @@ namespace Godot
return newRect;
}
+ /// <summary>
+ /// Returns true if this Rect2 completely encloses another one.
+ /// </summary>
+ /// <param name="b">The other rect that may be enclosed.</param>
+ /// <returns>A bool for whether or not this rect encloses `b`.</returns>
public bool Encloses(Rect2 b)
{
return b._position.x >= _position.x && b._position.y >= _position.y &&
@@ -71,6 +110,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 rect.</returns>
public Rect2 Expand(Vector2 to)
{
var expanded = this;
@@ -79,14 +123,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 +146,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 a given amount of units towards all the sides.
+ /// </summary>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown rect.</returns>
public Rect2 Grow(real_t by)
{
var g = this;
@@ -111,6 +172,14 @@ namespace Godot
return g;
}
+ /// <summary>
+ /// Returns a copy of the Rect2 grown a given amount of units towards each direction individually.
+ /// </summary>
+ /// <param name="left">The amount to grow by on the left.</param>
+ /// <param name="top">The amount to grow by on the top.</param>
+ /// <param name="right">The amount to grow by on the right.</param>
+ /// <param name="bottom">The amount to grow by on the bottom.</param>
+ /// <returns>The grown rect.</returns>
public Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom)
{
var g = this;
@@ -123,11 +192,17 @@ namespace Godot
return g;
}
+ /// <summary>
+ /// Returns a copy of the Rect2 grown a given amount of units towards the <see cref="Margin"/> direction.
+ /// </summary>
+ /// <param name="margin">The direction to grow in.</param>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown rect.</returns>
public Rect2 GrowMargin(Margin margin, real_t by)
{
var g = this;
- g.GrowIndividual(Margin.Left == margin ? by : 0,
+ g = g.GrowIndividual(Margin.Left == margin ? by : 0,
Margin.Top == margin ? by : 0,
Margin.Right == margin ? by : 0,
Margin.Bottom == margin ? by : 0);
@@ -135,11 +210,20 @@ namespace Godot
return g;
}
+ /// <summary>
+ /// Returns true if the Rect2 is flat or empty, or false otherwise.
+ /// </summary>
+ /// <returns>A bool for whether or not the rect 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 rect contains `point`.</returns>
public bool HasPoint(Vector2 point)
{
if (point.x < _position.x)
@@ -155,20 +239,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 rect 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 rect.</param>
+ /// <returns>The merged rect.</returns>
public Rect2 Merge(Rect2 b)
{
Rect2 newRect;
@@ -179,27 +308,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 +386,12 @@ namespace Godot
return _position.Equals(other._position) && _size.Equals(other._size);
}
+ /// <summary>
+ /// Returns true if this rect and `other` are approximately equal, by running
+ /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other rect to compare.</param>
+ /// <returns>Whether or not the rects are approximately equal.</returns>
public bool IsEqualApprox(Rect2 other)
{
return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs
index bc2cad8713..8f71c00d76 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs
@@ -3,6 +3,10 @@ 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>
@@ -10,29 +14,52 @@ namespace Godot
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 rect.
+ /// </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 rect.</returns>
public Rect2i Abs()
{
Vector2i end = End;
@@ -40,12 +67,19 @@ namespace Godot
return new Rect2i(topLeft, _size.Abs());
}
+ /// <summary>
+ /// Returns the intersection of this Rect2i and `b`.
+ /// </summary>
+ /// <param name="b">The other rect.</param>
+ /// <returns>The clipped rect.</returns>
public Rect2i Clip(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);
@@ -59,6 +93,11 @@ namespace Godot
return newRect;
}
+ /// <summary>
+ /// Returns true if this Rect2i completely encloses another one.
+ /// </summary>
+ /// <param name="b">The other rect that may be enclosed.</param>
+ /// <returns>A bool for whether or not this rect encloses `b`.</returns>
public bool Encloses(Rect2i b)
{
return b._position.x >= _position.x && b._position.y >= _position.y &&
@@ -66,6 +105,11 @@ namespace Godot
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 rect.</returns>
public Rect2i Expand(Vector2i to)
{
var expanded = this;
@@ -74,14 +118,22 @@ namespace Godot
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;
@@ -89,11 +141,20 @@ namespace Godot
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 a given amount of units towards all the sides.
+ /// </summary>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown rect.</returns>
public Rect2i Grow(int by)
{
var g = this;
@@ -106,6 +167,14 @@ namespace Godot
return g;
}
+ /// <summary>
+ /// Returns a copy of the Rect2i grown a given amount of units towards each direction individually.
+ /// </summary>
+ /// <param name="left">The amount to grow by on the left.</param>
+ /// <param name="top">The amount to grow by on the top.</param>
+ /// <param name="right">The amount to grow by on the right.</param>
+ /// <param name="bottom">The amount to grow by on the bottom.</param>
+ /// <returns>The grown rect.</returns>
public Rect2i GrowIndividual(int left, int top, int right, int bottom)
{
var g = this;
@@ -118,11 +187,17 @@ namespace Godot
return g;
}
+ /// <summary>
+ /// Returns a copy of the Rect2i grown a given amount of units towards the <see cref="Margin"/> direction.
+ /// </summary>
+ /// <param name="margin">The direction to grow in.</param>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown rect.</returns>
public Rect2i GrowMargin(Margin margin, int by)
{
var g = this;
- g.GrowIndividual(Margin.Left == margin ? by : 0,
+ g = g.GrowIndividual(Margin.Left == margin ? by : 0,
Margin.Top == margin ? by : 0,
Margin.Right == margin ? by : 0,
Margin.Bottom == margin ? by : 0);
@@ -130,11 +205,20 @@ namespace Godot
return g;
}
+ /// <summary>
+ /// Returns true if the Rect2 is flat or empty, or false otherwise.
+ /// </summary>
+ /// <returns>A bool for whether or not the rect 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 rect contains `point`.</returns>
public bool HasPoint(Vector2i point)
{
if (point.x < _position.x)
@@ -150,20 +234,49 @@ namespace Godot
return true;
}
- public bool Intersects(Rect2i b)
+ /// <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 rect 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 (_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 Rect2i that contains this Rect2 and `b`.
+ /// </summary>
+ /// <param name="b">The other rect.</param>
+ /// <returns>The merged rect.</returns>
public Rect2i Merge(Rect2i b)
{
Rect2i newRect;
@@ -174,27 +287,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 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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index 099eacd7dd..41b4e9367f 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;
@@ -237,10 +237,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;
@@ -287,14 +287,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)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
index aa8815d1aa..ac47f6029f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
@@ -8,11 +8,28 @@ using real_t = System.Single;
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 Transform : IEquatable<Transform>
{
+ /// <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>
@@ -85,13 +102,24 @@ namespace Godot
}
}
+ /// <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 Transform AffineInverse()
{
Basis basisInv = basis.Inverse();
return new Transform(basisInv, basisInv.Xform(-origin));
}
- public Transform InterpolateWith(Transform transform, 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 Transform InterpolateWith(Transform transform, real_t weight)
{
/* not sure if very "efficient" but good enough? */
@@ -104,18 +132,37 @@ namespace Godot
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);
+ interpolated.basis.SetQuatScale(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 Transform Inverse()
{
Basis basisTr = basis.Transposed();
return new Transform(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 Transform LookingAt(Vector3 target, Vector3 up)
{
var t = this;
@@ -123,22 +170,39 @@ namespace Godot
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 Transform Orthonormalized()
{
return new Transform(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 Transform Rotated(Vector3 axis, real_t phi)
{
return new Transform(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 Transform Scaled(Vector3 scale)
{
return new Transform(basis.Scaled(scale), origin * scale);
}
- public void SetLookAt(Vector3 eye, Vector3 target, Vector3 up)
+ private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up)
{
// Make rotation matrix
// Z vector
@@ -161,16 +225,30 @@ namespace Godot
origin = eye;
}
- public Transform Translated(Vector3 ofs)
+ /// <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 Transform Translated(Vector3 offset)
{
return new Transform(basis, new Vector3
(
- origin[0] += basis.Row0.Dot(ofs),
- origin[1] += basis.Row1.Dot(ofs),
- origin[2] += basis.Row2.Dot(ofs)
+ 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
@@ -181,6 +259,14 @@ namespace Godot
);
}
+ /// <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;
@@ -199,24 +285,58 @@ namespace Godot
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);
+ /// <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 Transform 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 Transform 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 Transform 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 Transform FlipZ { get { return _flipZ; } }
- // Constructors
+ /// <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 Transform(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="quat">The <see cref="Godot.Quat"/> to create the basis from.</param>
+ /// <param name="origin">The origin vector, or column index 3.</param>
public Transform(Quat quat, Vector3 origin)
{
basis = new Basis(quat);
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 Transform(Basis basis, Vector3 origin)
{
this.basis = basis;
@@ -255,6 +375,12 @@ namespace Godot
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(Transform other)
{
return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index e72a44809a..06bbe98497 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,8 +70,7 @@ 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;
}
@@ -112,6 +134,11 @@ namespace Godot
}
}
+ /// <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();
@@ -135,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));
@@ -172,28 +229,34 @@ namespace Godot
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;
@@ -208,6 +271,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;
@@ -225,11 +293,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;
@@ -257,6 +335,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;
@@ -264,11 +351,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;
@@ -280,11 +377,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;
+ /// <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; } }
- // Constructors
+ /// <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;
@@ -292,7 +408,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);
@@ -300,6 +425,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);
@@ -345,6 +475,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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index f7b13198f8..26bd828a5b 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,41 +90,80 @@ 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 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));
}
+ /// <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 Clamped(real_t length)
{
var v = this;
@@ -125,17 +178,30 @@ namespace Godot
return v;
}
+ /// <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)
{
return x * b.y - y * b.x;
}
+ /// <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="t">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 t)
{
- 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 t2 = t * t;
real_t t3 = t2 * t;
@@ -146,56 +212,153 @@ 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;
-
- res.x += t * (b.x - x);
- res.y += t * (b.y - y);
-
- return res;
- }
-
+ /// <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 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 +367,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 +378,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 +391,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,27 +404,59 @@ 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 * Dot(n) * 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();
}
+ /// <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));
}
+ /// <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;
@@ -256,23 +465,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.Stepify(x, step.x), Mathf.Stepify(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 Perpendicular()
{
return new Vector2(y, -x);
}
@@ -280,7 +523,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);
@@ -288,22 +530,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;
@@ -445,6 +723,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
index 7dc22d7918..8dd9ab2f0d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs
@@ -16,15 +16,29 @@ namespace Godot
[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
@@ -55,56 +69,102 @@ 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(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;
}
- public Vector2i Bounce(Vector2i n)
- {
- return -Reflect(n);
- }
-
+ /// <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;
@@ -113,6 +173,12 @@ namespace Godot
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;
@@ -121,16 +187,31 @@ namespace Godot
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.Y : Axis.X;
+ 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;
@@ -139,6 +220,11 @@ namespace Godot
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;
@@ -147,11 +233,12 @@ namespace Godot
return v;
}
- public Vector2i Reflect(Vector2i n)
- {
- return 2 * Dot(n) * n - this;
- }
-
+ /// <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;
@@ -160,9 +247,14 @@ namespace Godot
return v;
}
- public Vector2i Tangent()
+ /// <summary>
+ /// Returns a vector rotated 90 degrees counter-clockwise
+ /// compared to the original, with the same length.
+ /// </summary>
+ /// <returns>The perpendicular vector.</returns>
+ public Vector2 Perpendicular()
{
- return new Vector2i(y, -x);
+ return new Vector2(y, -x);
}
// Constants
@@ -174,25 +266,64 @@ namespace Godot
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; } }
- // Constructors
+ /// <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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
index a43836e985..d9b16a6afd 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,49 @@ 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 minimum 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(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 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,12 +154,21 @@ namespace Godot
);
}
+ /// <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="t">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 t)
{
- 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 t2 = t * t;
real_t t3 = t2 * t;
@@ -131,41 +180,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 +262,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 +277,78 @@ 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 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 +356,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 +370,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 +384,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,30 +398,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;
+ return 2.0f * Dot(normal) * normal - this;
}
- public Vector3 Round()
+ /// <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)
{
- return new Vector3(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z));
+#if DEBUG
+ if (!axis.IsNormalized())
+ {
+ throw new ArgumentException("Argument is not normalized", nameof(axis));
+ }
+#endif
+ return new Basis(axis, phi).Xform(this);
}
- public Vector3 Rotated(Vector3 axis, real_t phi)
+ /// <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()
{
- return new Basis(axis, phi).Xform(this);
+ 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;
@@ -279,44 +467,76 @@ namespace Godot
return v;
}
- public Vector3 Slerp(Vector3 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 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.Stepify(x, step.x),
+ Mathf.Stepify(y, step.y),
+ Mathf.Stepify(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);
@@ -326,25 +546,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;
@@ -505,6 +774,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
index c17f900131..bf25ba9cb3 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs
@@ -16,6 +16,10 @@ namespace Godot
[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,
@@ -23,10 +27,23 @@ namespace Godot
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
@@ -62,39 +79,51 @@ 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(int)"/> called on each component.</returns>
public Vector3i Abs()
{
- Vector3i v = this;
- if (v.x < 0)
- {
- v.x = -v.x;
- }
- if (v.y < 0)
- {
- v.y = -v.y;
- }
- if (v.z < 0)
- {
- v.z = -v.z;
- }
- return v;
+ return new Vector3i(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(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;
@@ -104,6 +133,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 int LengthSquared()
{
int x2 = x * x;
@@ -113,16 +148,31 @@ namespace Godot
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;
@@ -132,6 +182,11 @@ namespace Godot
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;
@@ -141,6 +196,12 @@ namespace Godot
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;
@@ -161,29 +222,81 @@ namespace Godot
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; } }
- // Constructors
+ /// <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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index ba0bbd7630..86a16c17f1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -1,38 +1,17 @@
-<?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\ExportAttribute.cs" />
@@ -52,6 +31,7 @@
<Compile Include="Core\Extensions\NodeExtensions.cs" />
<Compile Include="Core\Extensions\ObjectExtensions.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" />
@@ -88,5 +68,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..ab48904571 100644
--- a/modules/mono/glue/arguments_vector.h
+++ b/modules/mono/glue/arguments_vector.h
@@ -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 f370883320..ebcd6d5e9c 100644
--- a/modules/mono/glue/base_object_glue.cpp
+++ b/modules/mono/glue/base_object_glue.cpp
@@ -28,10 +28,10 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "base_object_glue.h"
-
#ifdef MONO_GLUE_ENABLED
+#include "core/class_db.h"
+#include "core/object.h"
#include "core/reference.h"
#include "core/string_name.h"
@@ -39,6 +39,7 @@
#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"
@@ -140,16 +141,18 @@ MethodBind *godot_icall_Object_ClassDB_get_method(StringName *p_type, MonoString
}
MonoObject *godot_icall_Object_weakref(Object *p_ptr) {
- if (!p_ptr)
+ if (!p_ptr) {
return nullptr;
+ }
Ref<WeakRef> wref;
Reference *ref = Object::cast_to<Reference>(p_ptr);
if (ref) {
REF r = ref;
- if (!r.is_valid())
+ if (!r.is_valid()) {
return nullptr;
+ }
wref.instance();
wref->set_ref(r);
@@ -241,6 +244,7 @@ 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_ConnectEventSignals", (void *)godot_icall_Object_ConnectEventSignals);
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);
diff --git a/modules/mono/glue/base_object_glue.h b/modules/mono/glue/base_object_glue.h
deleted file mode 100644
index 67769f3061..0000000000
--- a/modules/mono/glue/base_object_glue.h
+++ /dev/null
@@ -1,73 +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);
-
-void godot_icall_Object_ConnectEventSignals(Object *p_ptr);
-
-MethodBind *godot_icall_Object_ClassDB_get_method(StringName *p_type, MonoString *p_method);
-
-MonoObject *godot_icall_Object_weakref(Object *p_ptr);
-
-Error godot_icall_SignalAwaiter_connect(Object *p_source, StringName *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 4e3dc9c4ea..766b00d612 100644
--- a/modules/mono/glue/collections_glue.cpp
+++ b/modules/mono/glue/collections_glue.cpp
@@ -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/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() {
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 2da39a916a..5e892b616b 100644
--- a/modules/mono/glue/gd_glue.cpp
+++ b/modules/mono/glue/gd_glue.cpp
@@ -28,8 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "gd_glue.h"
-
#ifdef MONO_GLUE_ENABLED
#include "core/array.h"
@@ -40,6 +38,7 @@
#include "core/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) {
@@ -91,7 +90,6 @@ 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);
@@ -148,8 +146,9 @@ void godot_icall_GD_prints(MonoArray *p_what) {
return;
}
- if (i)
+ if (i) {
str += " ";
+ }
str += elem_str;
}
@@ -172,8 +171,9 @@ void godot_icall_GD_printt(MonoArray *p_what) {
return;
}
- if (i)
+ if (i) {
str += "\t";
+ }
str += elem_str;
}
@@ -198,7 +198,7 @@ double godot_icall_GD_rand_range(double from, double 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;
}
@@ -214,10 +214,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);
diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h
deleted file mode 100644
index 3ad6058205..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(StringName *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 ee99a300b9..c1f1936711 100644
--- a/modules/mono/glue/glue_header.h
+++ b/modules/mono/glue/glue_header.h
@@ -30,13 +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 "string_name_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
@@ -50,6 +53,7 @@ void godot_register_glue_header_icalls() {
godot_register_object_icalls();
godot_register_rid_icalls();
godot_register_string_icalls();
+ godot_register_scene_tree_icalls();
}
// Used by the generated glue
diff --git a/modules/mono/glue/nodepath_glue.cpp b/modules/mono/glue/nodepath_glue.cpp
index e413f404d8..2aa75dd309 100644
--- a/modules/mono/glue/nodepath_glue.cpp
+++ b/modules/mono/glue/nodepath_glue.cpp
@@ -28,12 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "nodepath_glue.h"
-
#ifdef MONO_GLUE_ENABLED
+#include "core/node_path.h"
#include "core/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();
}
diff --git a/modules/mono/glue/nodepath_glue.h b/modules/mono/glue/nodepath_glue.h
deleted file mode 100644
index 727679c278..0000000000
--- a/modules/mono/glue/nodepath_glue.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*************************************************************************/
-/* nodepath_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 NODEPATH_GLUE_H
-#define NODEPATH_GLUE_H
-
-#ifdef MONO_GLUE_ENABLED
-
-#include "core/node_path.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);
-
-MonoString *godot_icall_NodePath_get_name(NodePath *p_ptr, uint32_t p_idx);
-
-uint32_t godot_icall_NodePath_get_subname_count(NodePath *p_ptr);
-
-MonoString *godot_icall_NodePath_get_subname(NodePath *p_ptr, uint32_t p_idx);
-
-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();
-
-#endif // MONO_GLUE_ENABLED
-
-#endif // NODEPATH_GLUE_H
diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp
index 66a49d8fec..6d2e6b559f 100644
--- a/modules/mono/glue/rid_glue.cpp
+++ b/modules/mono/glue/rid_glue.cpp
@@ -28,17 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "rid_glue.h"
-
#ifdef MONO_GLUE_ENABLED
+#include "core/object.h"
#include "core/resource.h"
+#include "core/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);
}
diff --git a/modules/mono/glue/rid_glue.h b/modules/mono/glue/rid_glue.h
deleted file mode 100644
index 506d715451..0000000000
--- a/modules/mono/glue/rid_glue.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*************************************************************************/
-/* rid_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 RID_GLUE_H
-#define RID_GLUE_H
-
-#ifdef MONO_GLUE_ENABLED
-
-#include "core/object.h"
-#include "core/rid.h"
-
-#include "../mono_gd/gd_mono_marshal.h"
-
-RID *godot_icall_RID_Ctor(Object *p_from);
-
-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
diff --git a/modules/mono/glue/string_name_glue.h b/modules/mono/glue/scene_tree_glue.cpp
index 88354ddd84..b43daccc1b 100644
--- a/modules/mono/glue/string_name_glue.h
+++ b/modules/mono/glue/scene_tree_glue.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* string_name_glue.h */
+/* scene_tree_glue.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,27 +28,59 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef STRING_NAME_GLUE_H
-#define STRING_NAME_GLUE_H
-
#ifdef MONO_GLUE_ENABLED
+#include "core/array.h"
+#include "core/class_db.h"
#include "core/string_name.h"
+#include "scene/main/node.h"
+#include "scene/main/scene_tree.h"
+#include "../csharp_script.h"
#include "../mono_gd/gd_mono_marshal.h"
+#include "../mono_gd/gd_mono_utils.h"
-StringName *godot_icall_StringName_Ctor(MonoString *p_path);
+Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringName *group, MonoReflectionType *refltype) {
+ List<Node *> nodes;
+ Array ret;
-void godot_icall_StringName_Dtor(StringName *p_ptr);
+ // Retrieve all the nodes in the group
+ ptr->get_nodes_in_group(*group, &nodes);
-MonoString *godot_icall_StringName_operator_String(StringName *p_np);
+ // No need to bother if the group is empty
+ if (!nodes.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);
-MonoBoolean godot_icall_StringName_is_empty(StringName *p_ptr);
+ 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());
-// Register internal calls
+ if (si != nullptr) {
+ MonoObject *obj = si->get_mono_object();
+ if (obj != nullptr && mono_object_get_class(obj) == mono_class) {
+ ret.push_back(nodes[i]);
+ }
+ }
+ }
+ }
+ }
-void godot_register_string_name_icalls();
+ return memnew(Array(ret));
+}
-#endif // MONO_GLUE_ENABLED
+void godot_register_scene_tree_icalls() {
+ mono_add_internal_call("Godot.SceneTree::godot_icall_SceneTree_get_nodes_in_group_Generic", (void *)godot_icall_SceneTree_get_nodes_in_group_Generic);
+}
-#endif // STRING_NAME_GLUE_H
+#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp
index e407a70db9..595b8d71f1 100644
--- a/modules/mono/glue/string_glue.cpp
+++ b/modules/mono/glue/string_glue.cpp
@@ -28,14 +28,14 @@
/* 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 "../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();
// TODO Check possible Array/Vector<uint8_t> problem?
diff --git a/modules/mono/glue/string_glue.h b/modules/mono/glue/string_glue.h
deleted file mode 100644
index a5e833ba61..0000000000
--- a/modules/mono/glue/string_glue.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*************************************************************************/
-/* string_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 STRING_GLUE_H
-#define STRING_GLUE_H
-
-#ifdef MONO_GLUE_ENABLED
-
-#include "../mono_gd/gd_mono_marshal.h"
-
-MonoArray *godot_icall_String_md5_buffer(MonoString *p_str);
-
-MonoString *godot_icall_String_md5_text(MonoString *p_str);
-
-int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from);
-
-int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from);
-
-MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str);
-
-MonoString *godot_icall_String_sha256_text(MonoString *p_str);
-
-// Register internal calls
-
-void godot_register_string_icalls();
-
-#endif // MONO_GLUE_ENABLED
-
-#endif // STRING_GLUE_H
diff --git a/modules/mono/glue/string_name_glue.cpp b/modules/mono/glue/string_name_glue.cpp
index 81006e5849..4b2e88569b 100644
--- a/modules/mono/glue/string_name_glue.cpp
+++ b/modules/mono/glue/string_name_glue.cpp
@@ -28,12 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "string_name_glue.h"
-
#ifdef MONO_GLUE_ENABLED
+#include "core/string_name.h"
#include "core/ustring.h"
+#include "../mono_gd/gd_mono_marshal.h"
+
StringName *godot_icall_StringName_Ctor(MonoString *p_path) {
return memnew(StringName(GDMonoMarshal::mono_string_to_godot(p_path)));
}
@@ -48,7 +49,7 @@ MonoString *godot_icall_StringName_operator_String(StringName *p_np) {
}
MonoBoolean godot_icall_StringName_is_empty(StringName *p_ptr) {
- return (MonoBoolean)(p_ptr == StringName());
+ return (MonoBoolean)(*p_ptr == StringName());
}
void godot_register_string_name_icalls() {
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index d596163926..692da991c7 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -86,7 +86,6 @@ String _get_mono_user_dir() {
}
class _GodotSharpDirs {
-
public:
String res_data_dir;
String res_metadata_dir;
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index dfd78a8244..dbe9c7fc5d 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -48,8 +48,9 @@ bool ManagedCallable::compare_equal(const CallableCustom *p_a, const CallableCus
MonoDelegate *delegate_b = (MonoDelegate *)b->delegate_handle.get_target();
if (!delegate_a || !delegate_b) {
- if (!delegate_a && !delegate_b)
+ if (!delegate_a && !delegate_b) {
return true;
+ }
return false;
}
@@ -58,8 +59,9 @@ bool ManagedCallable::compare_equal(const CallableCustom *p_a, const CallableCus
}
bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
- if (compare_equal(p_a, p_b))
+ if (compare_equal(p_a, p_b)) {
return false;
+ }
return p_a < p_b;
}
@@ -82,6 +84,7 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const {
}
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();
}
diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp
index e362d5affb..16a6875406 100644
--- a/modules/mono/mono_gc_handle.cpp
+++ b/modules/mono/mono_gc_handle.cpp
@@ -56,11 +56,9 @@ MonoGCHandleData MonoGCHandleData::new_weak_handle(MonoObject *p_object) {
}
Ref<MonoGCHandleRef> MonoGCHandleRef::create_strong(MonoObject *p_object) {
-
return memnew(MonoGCHandleRef(MonoGCHandleData::new_strong_handle(p_object)));
}
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 fbcb405b0d..13cfad4654 100644
--- a/modules/mono/mono_gc_handle.h
+++ b/modules/mono/mono_gc_handle.h
@@ -47,8 +47,8 @@ enum class GCHandleType : char {
// Manual release of the GC handle must be done when using this struct
struct MonoGCHandleData {
- uint32_t handle;
- gdmono::GCHandleType type;
+ uint32_t handle = 0;
+ gdmono::GCHandleType type = gdmono::GCHandleType::NIL;
_FORCE_INLINE_ bool is_released() const { return !handle; }
_FORCE_INLINE_ bool is_weak() const { return type == gdmono::GCHandleType::WEAK_HANDLE; }
@@ -68,10 +68,7 @@ struct MonoGCHandleData {
MonoGCHandleData(const MonoGCHandleData &) = default;
- MonoGCHandleData() :
- handle(0),
- type(gdmono::GCHandleType::NIL) {
- }
+ MonoGCHandleData() {}
MonoGCHandleData(uint32_t p_handle, gdmono::GCHandleType p_type) :
handle(p_handle),
@@ -84,7 +81,6 @@ struct MonoGCHandleData {
};
class MonoGCHandleRef : public Reference {
-
GDCLASS(MonoGCHandleRef, Reference);
MonoGCHandleData data;
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 3298c5da4c..cf5ac33d20 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -130,9 +130,12 @@ void gd_mono_profiler_init() {
}
void gd_mono_debug_init() {
-
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);
@@ -141,8 +144,9 @@ void gd_mono_debug_init() {
if (Engine::get_singleton()->is_editor_hint() ||
ProjectSettings::get_singleton()->get_resource_path().empty() ||
Main::is_project_manager()) {
- if (da_args.size() == 0)
+ if (da_args.size() == 0) {
return;
+ }
}
if (da_args.length() == 0) {
@@ -247,7 +251,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();
@@ -310,7 +313,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...");
@@ -409,7 +411,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
@@ -422,22 +423,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;
}
@@ -467,6 +474,7 @@ uint64_t get_editor_api_hash() {
uint32_t get_bindings_version() {
GD_UNREACHABLE();
}
+
uint32_t get_cs_glue_version() {
GD_UNREACHABLE();
}
@@ -508,25 +516,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) {
-
- if (p_name == "mscorlib")
- return get_corlib_assembly();
+ 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;
+ 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);
@@ -537,27 +545,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, nullptr, &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 == nullptr, 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());
@@ -565,15 +573,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;
+ }
*r_assembly = assembly;
@@ -593,16 +601,19 @@ ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMo
if (nativecalls_klass) {
GDMonoField *api_hash_field = nativecalls_klass->get_field("godot_api_hash");
- if (api_hash_field)
+ 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)
+ 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)
+ 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;
@@ -613,21 +624,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);
@@ -648,12 +659,14 @@ 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)
+ 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)
+ 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) {
@@ -668,13 +681,15 @@ 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();
@@ -698,7 +713,6 @@ 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");
@@ -741,7 +755,6 @@ 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 ? \
@@ -769,8 +782,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");
@@ -798,9 +812,9 @@ 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
@@ -832,9 +846,9 @@ 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
@@ -864,30 +878,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;
}
@@ -895,8 +914,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();
@@ -909,7 +929,6 @@ 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)
@@ -962,9 +981,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);
@@ -974,9 +993,9 @@ 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);
@@ -994,7 +1013,6 @@ bool GDMono::_load_project_assembly() {
}
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");
@@ -1011,7 +1029,6 @@ void GDMono::_install_trace_listener() {
#ifndef GD_MONO_SINGLE_APPDOMAIN
Error GDMono::_load_scripts_domain() {
-
ERR_FAIL_COND_V(scripts_domain != nullptr, ERR_BUG);
print_verbose("Mono: Loading scripts domain...");
@@ -1026,13 +1043,13 @@ Error GDMono::_load_scripts_domain() {
}
Error GDMono::_unload_scripts_domain() {
-
ERR_FAIL_NULL_V(scripts_domain, ERR_BUG);
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;
@@ -1079,7 +1096,6 @@ Error GDMono::_unload_scripts_domain() {
#ifdef GD_MONO_HOT_RELOAD
Error GDMono::reload_scripts_domain() {
-
ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
if (scripts_domain) {
@@ -1116,7 +1132,6 @@ Error GDMono::reload_scripts_domain() {
#ifndef GD_MONO_SINGLE_APPDOMAIN
Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
-
CRASH_COND(p_domain == nullptr);
CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead
@@ -1124,8 +1139,9 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *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.");
@@ -1149,13 +1165,13 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
#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 = nullptr;
@@ -1163,9 +1179,9 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
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;
+ }
}
}
@@ -1173,27 +1189,27 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
}
GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) {
-
GDMonoClass *klass = corlib_assembly->get_class(p_namespace, p_name);
- if (klass)
+ 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 = nullptr;
while ((k = domain_assemblies.next(k))) {
GDMonoAssembly *assembly = domain_assemblies.get(*k);
klass = assembly->get_class(p_namespace, p_name);
- if (klass)
+ if (klass) {
return klass;
+ }
}
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 = nullptr;
@@ -1205,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 (EngineDebugger::is_active())
+ if (EngineDebugger::is_active()) {
EngineDebugger::get_singleton()->poll_events(false);
+ }
#endif
exit(mono_environment_exitcode_get());
@@ -1222,7 +1238,6 @@ void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) {
}
GDMono::GDMono() {
-
singleton = this;
gdmono_log = memnew(GDMonoLog);
@@ -1249,7 +1264,6 @@ GDMono::GDMono() {
}
GDMono::~GDMono() {
-
if (is_runtime_initialized()) {
#ifndef GD_MONO_SINGLE_APPDOMAIN
if (scripts_domain) {
@@ -1290,7 +1304,7 @@ GDMono::~GDMono() {
// Leave the rest to 'mono_jit_cleanup'
#endif
- const uint32_t *k = nullptr;
+ const int32_t *k = nullptr;
while ((k = assemblies.next(k))) {
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies.get(*k);
@@ -1314,8 +1328,9 @@ GDMono::~GDMono() {
gdmono::android::support::cleanup();
#endif
- if (gdmono_log)
+ if (gdmono_log) {
memdelete(gdmono_log);
+ }
singleton = nullptr;
}
@@ -1323,79 +1338,76 @@ GDMono::~GDMono() {
_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() != nullptr;
+ 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
+ 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())
+ 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);
@@ -1410,11 +1422,9 @@ void _GodotSharp::_bind_methods() {
}
_GodotSharp::_GodotSharp() {
-
singleton = this;
}
_GodotSharp::~_GodotSharp() {
-
singleton = nullptr;
}
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 4898833e8e..18f7418049 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -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(nullptr),
- 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;
@@ -149,7 +141,7 @@ private:
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
@@ -173,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
@@ -210,7 +204,7 @@ public:
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);
+ 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 */; }
@@ -241,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);
@@ -255,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 = nullptr;
+ prev_domain = nullptr;
}
}
~ScopeDomain() {
- if (prev_domain)
+ if (prev_domain) {
mono_domain_set(prev_domain, false);
+ }
}
};
@@ -284,8 +278,9 @@ public:
}
~ScopeExitDomainUnload() {
- if (domain)
+ if (domain) {
GDMono::get_singleton()->finalize_and_unload_domain(domain);
+ }
}
};
@@ -306,9 +301,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:
@@ -326,7 +318,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 0f211eebc6..9dbeee57ce 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -33,6 +33,7 @@
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/tokentype.h>
+#include "core/io/file_access_pack.h"
#include "core/list.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
@@ -45,7 +46,6 @@
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()) {
@@ -99,8 +99,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
// - 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.
-void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
-
+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);
@@ -109,8 +108,9 @@ void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data)
#ifdef GD_MONO_HOT_RELOAD
const char *path = mono_image_get_filename(image);
- if (FileAccess::exists(path))
+ if (FileAccess::exists(path)) {
gdassembly->modified_time = FileAccess::get_modified_time(path);
+ }
#endif
MonoDomain *domain = mono_domain_get();
@@ -133,30 +133,24 @@ 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");
GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
- if (loaded_asm)
+ if (loaded_asm) {
return loaded_asm->get_assembly();
+ }
return nullptr;
}
-MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
-
- (void)user_data; // UNUSED
-
+MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
- return _load_assembly_search(name, search_dirs, refonly);
+ return _load_assembly_search(name, aname, refonly, search_dirs);
}
-MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
-
+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;
@@ -168,23 +162,26 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const
if (has_extension) {
path = search_dir.plus_file(p_name);
if (FileAccess::exists(path)) {
- res = _real_load_assembly_from(path, p_refonly);
- if (res != nullptr)
+ 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 = _real_load_assembly_from(path, p_refonly);
- if (res != nullptr)
+ 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 = _real_load_assembly_from(path, p_refonly);
- if (res != nullptr)
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
+ if (res != nullptr) {
return res;
+ }
}
}
}
@@ -193,7 +190,6 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const
}
String GDMonoAssembly::find_assembly(const String &p_name) {
-
String path;
bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
@@ -203,16 +199,19 @@ 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;
+ }
}
}
@@ -220,7 +219,6 @@ String GDMonoAssembly::find_assembly(const String &p_name) {
}
void GDMonoAssembly::initialize() {
-
fill_search_dirs(search_dirs);
mono_install_assembly_search_hook(&assembly_search_hook, nullptr);
@@ -230,8 +228,7 @@ void GDMonoAssembly::initialize() {
mono_install_assembly_load_hook(&assembly_load_hook, nullptr);
}
-MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) {
-
+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.empty(), nullptr, "Could read the assembly in the specified location");
@@ -255,7 +252,33 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo
true, &status, p_refonly,
image_filename.utf8());
- ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from the loaded data");
+ 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];
+
+ 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;
@@ -264,8 +287,9 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo
if (!FileAccess::exists(pdb_path)) {
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);
@@ -292,8 +316,9 @@ no_pdb:
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)
+ if (!loaded_asm) {
assembly_load_hook(assembly, nullptr);
+ }
}
// Decrement refcount which was previously incremented by mono_image_open_from_data_with_name
@@ -303,7 +328,6 @@ no_pdb:
}
void GDMonoAssembly::unload() {
-
ERR_FAIL_NULL(image); // Should not be called if already unloaded
for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) {
@@ -322,20 +346,21 @@ String GDMonoAssembly::get_path() const {
}
GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) {
-
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)
+ if (!mono_class) {
return nullptr;
+ }
GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this));
@@ -346,13 +371,13 @@ GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const Stri
}
GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
-
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);
@@ -366,14 +391,14 @@ GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
}
GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) {
-
GDMonoClass *match = nullptr;
if (gdobject_class_cache_updated) {
Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class);
- if (result)
+ if (result) {
match = result->get();
+ }
} else {
List<GDMonoClass *> nested_classes;
@@ -382,30 +407,34 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
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))
+ if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
continue;
+ }
GDMonoClass *current = get_class(mono_class);
- if (!current)
+ if (!current) {
continue;
+ }
nested_classes.push_back(current);
- if (!match && current->get_name() == p_class)
+ if (!match && current->get_name() == p_class) {
match = current;
+ }
while (!nested_classes.empty()) {
GDMonoClass *current_nested = nested_classes.front()->get();
- nested_classes.pop_back();
+ nested_classes.pop_front();
void *iter = nullptr;
while (true) {
MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter);
- if (!raw_nested)
+ if (!raw_nested) {
break;
+ }
GDMonoClass *nested_class = get_class(raw_nested);
@@ -425,10 +454,30 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
return match;
}
-GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
+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();
+ }
+
+ // 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 (!assembly) {
+ assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs);
+ ERR_FAIL_NULL_V(assembly, nullptr);
+ }
+
+ 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;
+}
- if (p_name == "mscorlib" || p_name == "mscorlib.dll")
+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();
+ }
// 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());
@@ -447,18 +496,8 @@ GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_
return loaded_asm;
}
-GDMonoAssembly::GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) :
- name(p_name),
- image(p_image),
- assembly(p_assembly),
-#ifdef GD_MONO_HOT_RELOAD
- modified_time(0),
-#endif
- gdobject_class_cache_updated(false) {
-}
-
GDMonoAssembly::~GDMonoAssembly() {
-
- if (image)
+ if (image) {
unload();
+ }
}
diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h
index 43c8225b74..63899dc9be 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.h
+++ b/modules/mono/mono_gd/gd_mono_assembly.h
@@ -40,7 +40,6 @@
#include "gd_mono_utils.h"
class GDMonoAssembly {
-
struct ClassKey {
struct Hasher {
static _FORCE_INLINE_ uint32_t hash(const ClassKey &p_key) {
@@ -73,10 +72,10 @@ class GDMonoAssembly {
MonoAssembly *assembly;
#ifdef GD_MONO_HOT_RELOAD
- uint64_t modified_time;
+ uint64_t modified_time = 0;
#endif
- bool gdobject_class_cache_updated;
+ bool gdobject_class_cache_updated = false;
Map<StringName, GDMonoClass *> gdobject_class_cache;
HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
@@ -93,8 +92,8 @@ 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 MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly);
- static MonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
+ 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();
@@ -120,10 +119,16 @@ public:
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, MonoImage *p_image, MonoAssembly *p_assembly);
+ 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 5ddf18d544..29aef6e609 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -65,7 +65,6 @@ CachedData cached_data;
#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 = nullptr;
@@ -98,7 +97,6 @@ void CachedData::clear_corlib_cache() {
}
void CachedData::clear_godot_api_cache() {
-
godot_api_cache_updated = false;
rawclass_Dictionary = nullptr;
@@ -193,7 +191,6 @@ void CachedData::clear_godot_api_cache() {
#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()));
@@ -220,7 +217,7 @@ 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)", 1));
+ 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"));
@@ -228,7 +225,6 @@ void update_corlib_cache() {
}
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));
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 3cf2bd6ce5..a7bbc763a7 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -37,7 +37,6 @@
namespace GDMonoCache {
struct CachedData {
-
// -----------------------------------------------
// corlib classes
diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp
index 2c65f7e3a0..6575cbc1c8 100644
--- a/modules/mono/mono_gd/gd_mono_class.cpp
+++ b/modules/mono/mono_gd/gd_mono_class.cpp
@@ -79,19 +79,34 @@ 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) : 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) : 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 *>());
@@ -114,37 +129,38 @@ 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, nullptr);
#endif
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ 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 != nullptr);
attributes = mono_custom_attrs_from_class(get_mono_ptr());
@@ -152,11 +168,11 @@ void GDMonoClass::fetch_attributes() {
}
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 = nullptr;
MonoMethod *raw_method = nullptr;
@@ -168,7 +184,6 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
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_PRINT("Method '" + fullname + "' is hidden by Godot API method. Should be '" +
@@ -182,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());
@@ -195,8 +209,9 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
break;
}
- if (native_top == CACHED_CLASS(GodotObject))
+ if (native_top == CACHED_CLASS(GodotObject)) {
break;
+ }
native_top = native_top->get_parent_class();
}
@@ -205,8 +220,9 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
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
@@ -228,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();
}
@@ -246,46 +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);
const MethodKey *k = nullptr;
while ((k = methods.next(k))) {
- if (k->name == p_name)
+ if (k->name == p_name) {
return methods.get(*k);
+ }
}
return nullptr;
}
bool GDMonoClass::has_fetched_method_unknown_params(const StringName &p_name) {
-
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());
}
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, int 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)
+ 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);
@@ -300,7 +316,6 @@ GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_cou
}
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);
@@ -310,22 +325,21 @@ GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method) {
}
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, 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);
@@ -334,13 +348,13 @@ 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);
- if (!method)
+ if (!method) {
return nullptr;
+ }
ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, nullptr);
@@ -348,14 +362,15 @@ GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, boo
}
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)
+ if (fields_fetched) {
return nullptr;
+ }
MonoClassField *raw_field = mono_class_get_field_from_name(mono_class, String(p_name).utf8().get_data());
@@ -370,9 +385,9 @@ GDMonoField *GDMonoClass::get_field(const StringName &p_name) {
}
const Vector<GDMonoField *> &GDMonoClass::get_all_fields() {
-
- if (fields_fetched)
+ if (fields_fetched) {
return fields_list;
+ }
void *iter = nullptr;
MonoClassField *raw_field = nullptr;
@@ -396,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)
+ if (properties_fetched) {
return nullptr;
+ }
MonoProperty *raw_property = mono_class_get_property_from_name(mono_class, String(p_name).utf8().get_data());
@@ -418,9 +434,9 @@ GDMonoProperty *GDMonoClass::get_property(const StringName &p_name) {
}
const Vector<GDMonoProperty *> &GDMonoClass::get_all_properties() {
-
- if (properties_fetched)
+ if (properties_fetched) {
return properties_list;
+ }
void *iter = nullptr;
MonoProperty *raw_property = nullptr;
@@ -444,8 +460,9 @@ 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 = nullptr;
MonoClass *raw_class = nullptr;
@@ -471,7 +488,6 @@ const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() {
}
const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() {
-
if (!method_list_fetched) {
void *iter = nullptr;
MonoMethod *raw_method = nullptr;
@@ -486,7 +502,6 @@ 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;
@@ -503,7 +518,6 @@ GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name
}
GDMonoClass::~GDMonoClass() {
-
if (attributes) {
mono_custom_attrs_free(attributes);
}
diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h
index 9237aae057..44b146b87c 100644
--- a/modules/mono/mono_gd/gd_mono_class.h
+++ b/modules/mono/mono_gd/gd_mono_class.h
@@ -113,14 +113,15 @@ public:
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();
diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp
index e76cb84d43..563c45e71f 100644
--- a/modules/mono/mono_gd/gd_mono_field.cpp
+++ b/modules/mono/mono_gd/gd_mono_field.cpp
@@ -501,7 +501,8 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
case Variant::PACKED_COLOR_ARRAY: {
SET_FROM_ARRAY(PackedColorArray);
} break;
- default: break;
+ default:
+ break;
}
} break;
@@ -596,11 +597,13 @@ 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());
}
@@ -608,11 +611,13 @@ bool GDMonoField::has_attribute(GDMonoClass *p_attr_class) {
MonoObject *GDMonoField::get_attribute(GDMonoClass *p_attr_class) {
ERR_FAIL_NULL_V(p_attr_class, nullptr);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ if (!attributes) {
return nullptr;
+ }
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
}
diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h
index 61f2c8f071..5b40b439f9 100644
--- a/modules/mono/mono_gd/gd_mono_field.h
+++ b/modules/mono/mono_gd/gd_mono_field.h
@@ -36,7 +36,6 @@
#include "i_mono_class_member.h"
class GDMonoField : public IMonoClassMember {
-
GDMonoClass *owner;
MonoClassField *mono_field;
diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp
index 1898785699..fe1c2d28dd 100644
--- a/modules/mono/mono_gd/gd_mono_internals.cpp
+++ b/modules/mono/mono_gd/gd_mono_internals.cpp
@@ -45,7 +45,6 @@
namespace GDMonoInternals {
void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
-
// This method should not fail
CRASH_COND(!unmanaged);
@@ -115,7 +114,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
}
void unhandled_exception(MonoException *p_exc) {
- mono_unhandled_exception((MonoObject *)p_exc); // prints the exception as well
+ mono_print_unhandled_exception((MonoObject *)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
@@ -123,9 +122,10 @@ void unhandled_exception(MonoException *p_exc) {
GD_UNREACHABLE();
} else {
#ifdef DEBUG_ENABLED
- GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc);
- if (EngineDebugger::is_active())
+ GDMonoUtils::debug_send_unhandled_exception_error(p_exc);
+ if (EngineDebugger::is_active()) {
EngineDebugger::get_singleton()->poll_events(false);
+ }
#endif
}
}
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index ca16c2b76a..c5a988b8c3 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -51,13 +51,13 @@ GDMonoLog *GDMonoLog::singleton = nullptr;
#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", 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++;
}
@@ -65,7 +65,6 @@ static int get_log_level_id(const char *p_log_level) {
}
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)) {
@@ -94,7 +93,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 +104,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 +116,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,7 +134,6 @@ 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) {
@@ -175,7 +173,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,14 +181,12 @@ void GDMonoLog::initialize() {
}
GDMonoLog::GDMonoLog() {
-
singleton = this;
log_level_id = -1;
}
GDMonoLog::~GDMonoLog() {
-
singleton = nullptr;
if (log_file) {
@@ -207,12 +203,10 @@ void GDMonoLog::initialize() {
}
GDMonoLog::GDMonoLog() {
-
singleton = this;
}
GDMonoLog::~GDMonoLog() {
-
singleton = nullptr;
}
diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h
index 1fc21f7df5..3a52316060 100644
--- a/modules/mono/mono_gd/gd_mono_log.h
+++ b/modules/mono/mono_gd/gd_mono_log.h
@@ -45,7 +45,6 @@
#endif
class GDMonoLog {
-
#ifdef GD_MONO_LOG_ENABLED
int log_level_id;
diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp
index 91ee07388b..6d7d5f76cd 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.cpp
+++ b/modules/mono/mono_gd/gd_mono_marshal.cpp
@@ -72,92 +72,119 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_
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(Vector2i))
+ if (vtclass == CACHED_CLASS(Vector2i)) {
return Variant::VECTOR2I;
+ }
- if (vtclass == CACHED_CLASS(Rect2))
+ if (vtclass == CACHED_CLASS(Rect2)) {
return Variant::RECT2;
+ }
- if (vtclass == CACHED_CLASS(Rect2i))
+ 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))
+ 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))
+ if (vtclass == CACHED_CLASS(Quat)) {
return Variant::QUAT;
+ }
- if (vtclass == CACHED_CLASS(Transform))
+ if (vtclass == CACHED_CLASS(Transform)) {
return Variant::TRANSFORM;
+ }
- 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))
+ if (vtclass == CACHED_CLASS(Callable)) {
return Variant::CALLABLE;
+ }
- if (vtclass == CACHED_CLASS(SignalInfo))
+ if (vtclass == CACHED_CLASS(SignalInfo)) {
return Variant::SIGNAL;
+ }
- if (mono_class_is_enum(vtclass->get_mono_ptr()))
+ 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))
+ if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) {
return Variant::PACKED_BYTE_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int32_t))
+ if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
return Variant::PACKED_INT32_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int64_t))
+ if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) {
return Variant::PACKED_INT64_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(float))
+ if (array_type->eklass == CACHED_CLASS_RAW(float)) {
return Variant::PACKED_FLOAT32_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(double))
+ if (array_type->eklass == CACHED_CLASS_RAW(double)) {
return Variant::PACKED_FLOAT64_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(String))
+ if (array_type->eklass == CACHED_CLASS_RAW(String)) {
return Variant::PACKED_STRING_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2))
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
return Variant::PACKED_VECTOR2_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3))
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
return Variant::PACKED_VECTOR3_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Color))
+ 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))
+ if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) {
return Variant::ARRAY;
+ }
} break;
case MONO_TYPE_CLASS: {
@@ -201,8 +228,9 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_
} break;
case MONO_TYPE_OBJECT: {
- if (r_nil_is_variant)
+ if (r_nil_is_variant) {
*r_nil_is_variant = true;
+ }
return Variant::NIL;
} break;
@@ -244,8 +272,9 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_
} break;
}
- if (r_nil_is_variant)
+ if (r_nil_is_variant) {
*r_nil_is_variant = false;
+ }
// Unknown
return Variant::NIL;
@@ -282,31 +311,6 @@ 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());
-
- if (GDMonoUtils::Marshal::type_is_generic_dictionary(dict_reftype) ||
- GDMonoUtils::Marshal::type_is_system_generic_dictionary(dict_reftype) ||
- GDMonoUtils::Marshal::type_is_generic_idictionary(dict_reftype)) {
- MonoReflectionType *key_reftype;
- MonoReflectionType *value_reftype;
-
- GDMonoUtils::Marshal::dictionary_get_key_value_types(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;
- }
-
- return false;
-}
-
String mono_to_utf8_string(MonoString *p_mono_string) {
MonoError error;
char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error);
@@ -328,8 +332,9 @@ String mono_to_utf16_string(MonoString *p_mono_string) {
int len = mono_string_length(p_mono_string);
String ret;
- if (len == 0)
+ if (len == 0) {
return ret;
+ }
ret.resize(len + 1);
ret.set(len, 0);
@@ -409,8 +414,9 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
case MONO_TYPE_STRING: {
- if (p_var->get_type() == Variant::NIL)
+ if (p_var->get_type() == Variant::NIL) {
return nullptr; // Otherwise, Variant -> String would return the string "Null"
+ }
return (MonoObject *)mono_string_from_godot(p_var->operator String());
} break;
@@ -547,39 +553,50 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
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 (MonoObject *)Array_to_mono_array(p_var->operator Array());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(uint8_t))
+ if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) {
return (MonoObject *)PackedByteArray_to_mono_array(p_var->operator PackedByteArray());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int32_t))
+ if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
return (MonoObject *)PackedInt32Array_to_mono_array(p_var->operator PackedInt32Array());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int64_t))
+ if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) {
return (MonoObject *)PackedInt64Array_to_mono_array(p_var->operator PackedInt64Array());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(float))
+ if (array_type->eklass == CACHED_CLASS_RAW(float)) {
return (MonoObject *)PackedFloat32Array_to_mono_array(p_var->operator PackedFloat32Array());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(double))
+ if (array_type->eklass == CACHED_CLASS_RAW(double)) {
return (MonoObject *)PackedFloat64Array_to_mono_array(p_var->operator PackedFloat64Array());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(String))
+ if (array_type->eklass == CACHED_CLASS_RAW(String)) {
return (MonoObject *)PackedStringArray_to_mono_array(p_var->operator PackedStringArray());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2))
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
return (MonoObject *)PackedVector2Array_to_mono_array(p_var->operator PackedVector2Array());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3))
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
return (MonoObject *)PackedVector3Array_to_mono_array(p_var->operator PackedVector3Array());
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Color))
+ if (array_type->eklass == CACHED_CLASS_RAW(Color)) {
return (MonoObject *)PackedColorArray_to_mono_array(p_var->operator PackedColorArray());
+ }
GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(array_type->eklass);
- if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class))
+ if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) {
return (MonoObject *)Array_to_mono_array(p_var->operator Array(), array_type_class);
+ }
ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to a managed array of unmarshallable element type.");
} break;
@@ -624,8 +641,8 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
return BOX_BOOLEAN(val);
}
case Variant::INT: {
- int32_t val = p_var->operator signed int();
- return BOX_INT32(val);
+ int64_t val = p_var->operator int64_t();
+ return BOX_INT64(val);
}
case Variant::FLOAT: {
#ifdef REAL_T_IS_DOUBLE
@@ -787,7 +804,6 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
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());
switch (p_type.type_encoding) {
@@ -821,100 +837,128 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type
return unbox<double>(p_obj);
case MONO_TYPE_STRING: {
- if (p_obj == nullptr)
+ if (p_obj == nullptr) {
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))
+ if (vtclass == CACHED_CLASS(Vector2)) {
return MARSHALLED_IN(Vector2, unbox_addr<GDMonoMarshal::M_Vector2>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Vector2i))
+ if (vtclass == CACHED_CLASS(Vector2i)) {
return MARSHALLED_IN(Vector2i, unbox_addr<GDMonoMarshal::M_Vector2i>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Rect2))
+ if (vtclass == CACHED_CLASS(Rect2)) {
return MARSHALLED_IN(Rect2, unbox_addr<GDMonoMarshal::M_Rect2>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Rect2i))
+ if (vtclass == CACHED_CLASS(Rect2i)) {
return MARSHALLED_IN(Rect2i, unbox_addr<GDMonoMarshal::M_Rect2i>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Transform2D))
+ if (vtclass == CACHED_CLASS(Transform2D)) {
return MARSHALLED_IN(Transform2D, unbox_addr<GDMonoMarshal::M_Transform2D>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Vector3))
+ if (vtclass == CACHED_CLASS(Vector3)) {
return MARSHALLED_IN(Vector3, unbox_addr<GDMonoMarshal::M_Vector3>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Vector3i))
+ if (vtclass == CACHED_CLASS(Vector3i)) {
return MARSHALLED_IN(Vector3i, unbox_addr<GDMonoMarshal::M_Vector3i>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Basis))
+ if (vtclass == CACHED_CLASS(Basis)) {
return MARSHALLED_IN(Basis, unbox_addr<GDMonoMarshal::M_Basis>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Quat))
+ if (vtclass == CACHED_CLASS(Quat)) {
return MARSHALLED_IN(Quat, unbox_addr<GDMonoMarshal::M_Quat>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Transform))
+ if (vtclass == CACHED_CLASS(Transform)) {
return MARSHALLED_IN(Transform, unbox_addr<GDMonoMarshal::M_Transform>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(AABB))
+ if (vtclass == CACHED_CLASS(AABB)) {
return MARSHALLED_IN(AABB, unbox_addr<GDMonoMarshal::M_AABB>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Color))
+ if (vtclass == CACHED_CLASS(Color)) {
return MARSHALLED_IN(Color, unbox_addr<GDMonoMarshal::M_Color>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Plane))
+ if (vtclass == CACHED_CLASS(Plane)) {
return MARSHALLED_IN(Plane, unbox_addr<GDMonoMarshal::M_Plane>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Callable))
+ if (vtclass == CACHED_CLASS(Callable)) {
return managed_to_callable(unbox<GDMonoMarshal::M_Callable>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(SignalInfo))
+ 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()))
+ if (mono_class_is_enum(vtclass->get_mono_ptr())) {
return unbox<int32_t>(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))
+ 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))
+ if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
return mono_array_to_PackedInt32Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int64_t))
+ if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) {
return mono_array_to_PackedInt64Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(float))
+ if (array_type->eklass == CACHED_CLASS_RAW(float)) {
return mono_array_to_PackedFloat32Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(double))
+ if (array_type->eklass == CACHED_CLASS_RAW(double)) {
return mono_array_to_PackedFloat64Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(String))
+ if (array_type->eklass == CACHED_CLASS_RAW(String)) {
return mono_array_to_PackedStringArray((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2))
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
return mono_array_to_PackedVector2Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3))
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
return mono_array_to_PackedVector3Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Color))
+ 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))
+ 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.");
@@ -1013,8 +1057,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));
@@ -1022,20 +1067,26 @@ 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);
@@ -1045,8 +1096,9 @@ String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc) {
MonoString *mono_str = GDMonoUtils::object_to_string(p_obj, &exc);
if (exc) {
- if (r_exc)
+ if (r_exc) {
*r_exc = exc;
+ }
return String();
}
@@ -1156,8 +1208,9 @@ MonoArray *Array_to_mono_array(const Array &p_array, GDMonoClass *p_array_type_c
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);
@@ -1175,22 +1228,23 @@ MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), length);
- int32_t *dst = (int32_t *)mono_array_addr(ret, int32_t, 0);
- memcpy(dst, src, length);
+ int32_t *dst = mono_array_addr(ret, int32_t, 0);
+ memcpy(dst, src, length * sizeof(int32_t));
return ret;
}
PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array) {
PackedInt32Array ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
int32_t *dst = ret.ptrw();
- const int32_t *src = (const int32_t *)mono_array_addr(p_array, int32_t, 0);
- memcpy(dst, src, length);
+ const int32_t *src = mono_array_addr(p_array, int32_t, 0);
+ memcpy(dst, src, length * sizeof(int32_t));
return ret;
}
@@ -1201,22 +1255,23 @@ MonoArray *PackedInt64Array_to_mono_array(const PackedInt64Array &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int64_t), length);
- int64_t *dst = (int64_t *)mono_array_addr(ret, int64_t, 0);
- memcpy(dst, src, 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)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
int64_t *dst = ret.ptrw();
- const int64_t *src = (const int64_t *)mono_array_addr(p_array, int64_t, 0);
- memcpy(dst, src, length);
+ const int64_t *src = mono_array_addr(p_array, int64_t, 0);
+ memcpy(dst, src, length * sizeof(int64_t));
return ret;
}
@@ -1227,22 +1282,23 @@ MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), length);
- uint8_t *dst = (uint8_t *)mono_array_addr(ret, uint8_t, 0);
- memcpy(dst, src, length);
+ uint8_t *dst = mono_array_addr(ret, uint8_t, 0);
+ memcpy(dst, src, length * sizeof(uint8_t));
return ret;
}
PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array) {
PackedByteArray ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
uint8_t *dst = ret.ptrw();
- const uint8_t *src = (const uint8_t *)mono_array_addr(p_array, uint8_t, 0);
- memcpy(dst, src, length);
+ const uint8_t *src = mono_array_addr(p_array, uint8_t, 0);
+ memcpy(dst, src, length * sizeof(uint8_t));
return ret;
}
@@ -1253,22 +1309,23 @@ MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(float), length);
- float *dst = (float *)mono_array_addr(ret, float, 0);
- memcpy(dst, src, length);
+ float *dst = mono_array_addr(ret, float, 0);
+ memcpy(dst, src, length * sizeof(float));
return ret;
}
PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array) {
PackedFloat32Array ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
float *dst = ret.ptrw();
- const float *src = (const float *)mono_array_addr(p_array, float, 0);
- memcpy(dst, src, length);
+ const float *src = mono_array_addr(p_array, float, 0);
+ memcpy(dst, src, length * sizeof(float));
return ret;
}
@@ -1279,22 +1336,23 @@ MonoArray *PackedFloat64Array_to_mono_array(const PackedFloat64Array &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(double), length);
- double *dst = (double *)mono_array_addr(ret, double, 0);
- memcpy(dst, src, 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)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
double *dst = ret.ptrw();
- const double *src = (const double *)mono_array_addr(p_array, double, 0);
- memcpy(dst, src, length);
+ const double *src = mono_array_addr(p_array, double, 0);
+ memcpy(dst, src, length * sizeof(double));
return ret;
}
@@ -1315,8 +1373,9 @@ MonoArray *PackedStringArray_to_mono_array(const PackedStringArray &p_array) {
PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array) {
PackedStringArray ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
String *w = ret.ptrw();
@@ -1336,8 +1395,8 @@ MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), length);
if constexpr (InteropLayout::MATCHES_Color) {
- Color *dst = (Color *)mono_array_addr(ret, Color, 0);
- memcpy(dst, src, length);
+ 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);
@@ -1350,15 +1409,16 @@ MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array) {
PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array) {
PackedColorArray ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
Color *dst = ret.ptrw();
if constexpr (InteropLayout::MATCHES_Color) {
- const Color *src = (const Color *)mono_array_addr(p_array, Color, 0);
- memcpy(dst, src, length);
+ 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));
@@ -1375,8 +1435,8 @@ MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), length);
if constexpr (InteropLayout::MATCHES_Vector2) {
- Vector2 *dst = (Vector2 *)mono_array_addr(ret, Vector2, 0);
- memcpy(dst, src, length);
+ 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);
@@ -1389,15 +1449,16 @@ MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array) {
PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array) {
PackedVector2Array ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
Vector2 *dst = ret.ptrw();
if constexpr (InteropLayout::MATCHES_Vector2) {
- const Vector2 *src = (const Vector2 *)mono_array_addr(p_array, Vector2, 0);
- memcpy(dst, src, length);
+ 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));
@@ -1414,8 +1475,8 @@ MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array) {
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), length);
if constexpr (InteropLayout::MATCHES_Vector3) {
- Vector3 *dst = (Vector3 *)mono_array_addr(ret, Vector3, 0);
- memcpy(dst, src, length);
+ 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);
@@ -1428,15 +1489,16 @@ MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array) {
PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array) {
PackedVector3Array ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
Vector3 *dst = ret.ptrw();
if constexpr (InteropLayout::MATCHES_Vector3) {
- const Vector3 *src = (const Vector3 *)mono_array_addr(p_array, Vector3, 0);
- memcpy(dst, src, length);
+ 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));
diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h
index f2d887e6d6..4ff330fd43 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.h
+++ b/modules/mono/mono_gd/gd_mono_marshal.h
@@ -66,7 +66,6 @@ T *unbox_addr(MonoObject *p_obj) {
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
@@ -74,15 +73,17 @@ 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)
+ if constexpr (sizeof(CharType) == 2) {
return mono_to_utf16_string(p_mono_string);
+ }
return mono_to_utf8_string(p_mono_string);
}
_FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) {
- if (p_mono_string == nullptr)
+ if (p_mono_string == nullptr) {
return String();
+ }
return mono_string_to_godot_not_null(p_mono_string);
}
@@ -96,8 +97,9 @@ _FORCE_INLINE_ MonoString *mono_from_utf16_string(const String &p_string) {
}
_FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) {
- if (sizeof(CharType) == 2)
+ if constexpr (sizeof(CharType) == 2) {
return mono_from_utf16_string(p_string);
+ }
return mono_from_utf8_string(p_string);
}
diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp
index 432aa74a6f..04f3b25a70 100644
--- a/modules/mono/mono_gd/gd_mono_method.cpp
+++ b/modules/mono/mono_gd/gd_mono_method.cpp
@@ -155,11 +155,13 @@ 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());
}
@@ -167,11 +169,13 @@ bool GDMonoMethod::has_attribute(GDMonoClass *p_attr_class) {
MonoObject *GDMonoMethod::get_attribute(GDMonoClass *p_attr_class) {
ERR_FAIL_NULL_V(p_attr_class, nullptr);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ if (!attributes) {
return nullptr;
+ }
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
}
@@ -245,14 +249,14 @@ void GDMonoMethod::get_parameter_types(Vector<ManagedType> &types) const {
}
const MethodInfo &GDMonoMethod::get_method_info() {
-
if (!method_info_fetched) {
method_info.name = name;
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)
+ 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);
@@ -260,8 +264,9 @@ const MethodInfo &GDMonoMethod::get_method_info() {
for (int i = 0; i < params_count; ++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)
+ if (arg_info.type == Variant::NIL && nil_is_variant) {
arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
method_info.arguments.push_back(arg_info);
}
diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h
index 54b2eba3e8..f78f57dca0 100644
--- a/modules/mono/mono_gd/gd_mono_method.h
+++ b/modules/mono/mono_gd/gd_mono_method.h
@@ -36,7 +36,6 @@
#include "i_mono_class_member.h"
class GDMonoMethod : public IMonoClassMember {
-
StringName name;
int params_count;
diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h
index 0e05e974e9..01f3ae342a 100644
--- a/modules/mono/mono_gd/gd_mono_method_thunk.h
+++ b/modules/mono/mono_gd/gd_mono_method_thunk.h
@@ -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) {
@@ -81,9 +80,7 @@ public:
mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr());
}
- GDMonoMethodThunk() :
- mono_method_thunk(nullptr) {
- }
+ 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) {
@@ -127,9 +123,7 @@ public:
mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr());
}
- GDMonoMethodThunkR() :
- mono_method_thunk(nullptr) {
- }
+ GDMonoMethodThunkR() {}
explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) {
#ifdef DEBUG_ENABLED
@@ -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) {
@@ -277,9 +270,7 @@ public:
mono_method = p_mono_method;
}
- GDMonoMethodThunk() :
- mono_method(nullptr) {
- }
+ 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) {
@@ -318,9 +308,7 @@ public:
mono_method = p_mono_method;
}
- GDMonoMethodThunkR() :
- mono_method(nullptr) {
- }
+ 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 c3e7598f2d..bc3be97102 100644
--- a/modules/mono/mono_gd/gd_mono_property.cpp
+++ b/modules/mono/mono_gd/gd_mono_property.cpp
@@ -77,15 +77,17 @@ GDMonoProperty::~GDMonoProperty() {
bool GDMonoProperty::is_static() {
MonoMethod *prop_method = mono_property_get_get_method(mono_property);
- if (prop_method == nullptr)
+ if (prop_method == nullptr) {
prop_method = mono_property_get_set_method(mono_property);
+ }
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 == nullptr)
+ if (prop_method == nullptr) {
prop_method = mono_property_get_set_method(mono_property);
+ }
switch (mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) {
case MONO_METHOD_ATTR_PRIVATE:
@@ -106,11 +108,13 @@ 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());
}
@@ -118,11 +122,13 @@ bool GDMonoProperty::has_attribute(GDMonoClass *p_attr_class) {
MonoObject *GDMonoProperty::get_attribute(GDMonoClass *p_attr_class) {
ERR_FAIL_NULL_V(p_attr_class, nullptr);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ if (!attributes) {
return nullptr;
+ }
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
}
diff --git a/modules/mono/mono_gd/gd_mono_property.h b/modules/mono/mono_gd/gd_mono_property.h
index 4653758a86..611ac293e4 100644
--- a/modules/mono/mono_gd/gd_mono_property.h
+++ b/modules/mono/mono_gd/gd_mono_property.h
@@ -36,7 +36,6 @@
#include "i_mono_class_member.h"
class GDMonoProperty : public IMonoClassMember {
-
GDMonoClass *owner;
MonoProperty *mono_property;
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index f9d492dabb..3f1155f430 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -38,7 +38,6 @@
#include "core/os/dir_access.h"
#include "core/os/mutex.h"
#include "core/os/os.h"
-#include "core/project_settings.h"
#include "core/reference.h"
#ifdef TOOLS_ENABLED
@@ -51,14 +50,13 @@
#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)
+ if (!unmanaged) {
return nullptr;
+ }
if (unmanaged->get_script_instance()) {
CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance());
@@ -91,8 +89,9 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
MonoObject *target = gchandle.get_target();
- if (target)
+ if (target) {
return target;
+ }
CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
@@ -197,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);
@@ -221,11 +221,14 @@ 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()) != nullptr);
@@ -386,14 +389,6 @@ String get_exception_name_and_message(MonoException *p_exc) {
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, nullptr);
-}
-
void debug_print_unhandled_exception(MonoException *p_exc) {
print_unhandled_exception(p_exc);
debug_send_unhandled_exception_error(p_exc);
@@ -411,8 +406,9 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
}
static thread_local bool _recursion_flag_ = false;
- if (_recursion_flag_)
+ if (_recursion_flag_) {
return;
+ }
_recursion_flag_ = true;
SCOPE_EXIT { _recursion_flag_ = false; };
@@ -442,8 +438,9 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
Vector<ScriptLanguage::StackInfo> _si;
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);
@@ -453,8 +450,9 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
CRASH_COND(inner_exc_prop == nullptr);
MonoObject *inner_exc = inner_exc_prop->get_value((MonoObject *)p_exc);
- if (inner_exc != nullptr)
+ if (inner_exc != nullptr) {
si.insert(0, separator);
+ }
p_exc = (MonoException *)inner_exc;
}
@@ -564,9 +562,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) \
@@ -663,8 +662,7 @@ GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, Mon
} // namespace Marshal
-ScopeThreadAttach::ScopeThreadAttach() :
- mono_thread(nullptr) {
+ScopeThreadAttach::ScopeThreadAttach() {
if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) {
mono_thread = GDMonoUtils::attach_current_thread();
}
diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h
index e3011ade5d..9db4a5f3f0 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -84,10 +84,6 @@ 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() != nullptr && 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);
@@ -115,7 +111,6 @@ 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);
@@ -134,6 +129,7 @@ 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;
}
@@ -155,7 +151,7 @@ struct ScopeThreadAttach {
~ScopeThreadAttach();
private:
- MonoThread *mono_thread;
+ MonoThread *mono_thread = nullptr;
};
StringName get_native_godot_class_name(GDMonoClass *p_class);
diff --git a/modules/mono/mono_gd/managed_type.h b/modules/mono/mono_gd/managed_type.h
index 84d1837853..491a2f3d20 100644
--- a/modules/mono/mono_gd/managed_type.h
+++ b/modules/mono/mono_gd/managed_type.h
@@ -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(nullptr) {
- }
+ ManagedType() {}
ManagedType(int p_type_encoding, GDMonoClass *p_type_class) :
type_encoding(p_type_encoding),
diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp
index 8bcdeec9dd..8bcdeec9dd 100755..100644
--- a/modules/mono/mono_gd/support/android_support.cpp
+++ b/modules/mono/mono_gd/support/android_support.cpp
diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm
index e3d1a647fd..e3d1a647fd 100755..100644
--- a/modules/mono/mono_gd/support/ios_support.mm
+++ b/modules/mono/mono_gd/support/ios_support.mm
diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp
index 94431e7c30..98c3ba1324 100644
--- a/modules/mono/register_types.cpp
+++ b/modules/mono/register_types.cpp
@@ -62,8 +62,9 @@ void register_mono_types() {
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/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp
index e77a2e98f2..bd67b03c8e 100644
--- a/modules/mono/signal_awaiter_utils.cpp
+++ b/modules/mono/signal_awaiter_utils.cpp
@@ -51,18 +51,21 @@ bool SignalAwaiterCallable::compare_equal(const CallableCustom *p_a, const Calla
const SignalAwaiterCallable *a = static_cast<const SignalAwaiterCallable *>(p_a);
const SignalAwaiterCallable *b = static_cast<const SignalAwaiterCallable *>(p_b);
- if (a->target_id != b->target_id)
+ if (a->target_id != b->target_id) {
return false;
+ }
- if (a->signal != b->signal)
+ if (a->signal != b->signal) {
return false;
+ }
return true;
}
bool SignalAwaiterCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
- if (compare_equal(p_a, p_b))
+ if (compare_equal(p_a, p_b)) {
return false;
+ }
return p_a < p_b;
}
@@ -77,7 +80,6 @@ String SignalAwaiterCallable::get_as_text() const {
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);
@@ -146,18 +148,21 @@ bool EventSignalCallable::compare_equal(const CallableCustom *p_a, const Callabl
const EventSignalCallable *a = static_cast<const EventSignalCallable *>(p_a);
const EventSignalCallable *b = static_cast<const EventSignalCallable *>(p_b);
- if (a->owner != b->owner)
+ if (a->owner != b->owner) {
return false;
+ }
- if (a->event_signal != b->event_signal)
+ if (a->event_signal != b->event_signal) {
return false;
+ }
return true;
}
bool EventSignalCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
- if (compare_equal(p_a, p_b))
+ if (compare_equal(p_a, p_b)) {
return false;
+ }
return p_a < p_b;
}
diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h
index 8650d6cc09..dc542477f5 100644
--- a/modules/mono/utils/macros.h
+++ b/modules/mono/utils/macros.h
@@ -68,6 +68,6 @@ public:
} // namespace gdmono
#define SCOPE_EXIT \
- auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]()
+ 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 8f0ad8ba5e..e0cf916a01 100644
--- a/modules/mono/utils/mono_reg_utils.cpp
+++ b/modules/mono/utils/mono_reg_utils.cpp
@@ -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,7 +67,6 @@ 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();
@@ -77,7 +75,6 @@ LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value)
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, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize);
}
@@ -92,7 +89,6 @@ 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);
@@ -128,7 +124,6 @@ cleanup:
}
LONG _find_mono_in_reg_old(const String &p_subkey, MonoRegInfo &r_info) {
-
String default_clr;
HKEY hKey;
@@ -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
@@ -232,6 +225,7 @@ cleanup:
return msbuild_tools_path;
}
+
} // namespace MonoRegUtils
#endif // WINDOWS_ENABLED
diff --git a/modules/mono/utils/mono_reg_utils.h b/modules/mono/utils/mono_reg_utils.h
index f844a7233a..4ef876f2b6 100644
--- a/modules/mono/utils/mono_reg_utils.h
+++ b/modules/mono/utils/mono_reg_utils.h
@@ -36,7 +36,6 @@
#include "core/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 8fadf3c109..e68466b1cf 100644
--- a/modules/mono/utils/osx_utils.cpp
+++ b/modules/mono/utils/osx_utils.cpp
@@ -38,25 +38,21 @@
#include <CoreServices/CoreServices.h>
bool osx_is_app_bundle_installed(const String &p_bundle_id) {
-
- CFURLRef app_url = nullptr;
CFStringRef bundle_id = CFStringCreateWithCString(nullptr, p_bundle_id.utf8(), kCFStringEncodingUTF8);
- OSStatus result = LSFindApplicationForInfo(kLSUnknownCreator, bundle_id, nullptr, nullptr, &app_url);
+ 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/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index 973375a471..ccfaf5aba7 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -50,34 +50,6 @@
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, nullptr);
@@ -90,12 +62,14 @@ String cwd() {
return buffer.simplify_path();
#else
char buffer[PATH_MAX];
- if (::getcwd(buffer, sizeof(buffer)) == nullptr)
+ 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
@@ -135,23 +109,26 @@ String realpath(const String &p_path) {
#elif UNIX_ENABLED
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.empty()) {
return p_b;
+ }
const CharType a_last = p_a[p_a.length() - 1];
if ((a_last == '/' || a_last == '\\') ||
@@ -178,8 +155,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.empty() || base_dir.ends_with(":"))) {
return p_path;
+ }
return String("..").plus_file(relative_to_impl(p_path, base_dir));
}
diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h
index 9965f58b0a..bcd8af8bb9 100644
--- a/modules/mono/utils/path_utils.h
+++ b/modules/mono/utils/path_utils.h
@@ -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();
diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp
index 907811355f..f8d9804de4 100644
--- a/modules/mono/utils/string_utils.cpp
+++ b/modules/mono/utils/string_utils.cpp
@@ -38,14 +38,16 @@
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();
@@ -75,17 +77,20 @@ int sfind(const String &p_text, int p_from) {
}
}
- if (found)
+ if (found) {
return i;
+ }
}
return -1;
}
+
} // 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;
@@ -132,7 +137,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" ||
@@ -196,16 +200,6 @@ 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)