summaryrefslogtreecommitdiff
path: root/modules/mono
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono')
-rw-r--r--modules/mono/SCsub33
-rw-r--r--modules/mono/__init__.py0
-rw-r--r--modules/mono/build_scripts/api_solution_build.py66
-rw-r--r--modules/mono/build_scripts/godot_tools_build.py108
-rw-r--r--modules/mono/build_scripts/make_android_mono_config.py69
-rw-r--r--modules/mono/build_scripts/make_cs_compressed_header.py21
-rw-r--r--modules/mono/build_scripts/mono_configure.py199
-rw-r--r--modules/mono/build_scripts/patches/fix-mono-android-tkill.diff70
-rw-r--r--modules/mono/build_scripts/solution_builder.py (renamed from modules/mono/build_scripts/godotsharptools_build.py)143
-rw-r--r--modules/mono/config.py10
-rw-r--r--modules/mono/csharp_script.cpp682
-rw-r--r--modules/mono/csharp_script.h50
-rw-r--r--modules/mono/doc_classes/@C#.xml2
-rw-r--r--modules/mono/doc_classes/CSharpScript.xml2
-rw-r--r--modules/mono/doc_classes/GodotSharp.xml2
-rw-r--r--modules/mono/editor/GodotSharpTools/.gitignore2
-rw-r--r--modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs425
-rw-r--r--modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs74
-rw-r--r--modules/mono/editor/GodotSharpTools/GodotSharpTools.sln17
-rw-r--r--modules/mono/editor/GodotSharpTools/Utils/OS.cs62
-rw-r--r--modules/mono/editor/GodotTools/.gitignore356
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs186
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj59
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj38
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs38
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs (renamed from modules/mono/editor/GodotSharpTools/StringExtensions.cs)33
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs15
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs122
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj (renamed from modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj)41
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs (renamed from modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs)14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs (renamed from modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs)3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs (renamed from modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs)43
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs (renamed from modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs)5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs (renamed from modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs)4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config (renamed from modules/mono/editor/GodotSharpTools/packages.config)2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs172
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs210
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs115
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs396
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs538
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs197
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj81
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs47
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs87
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs50
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs91
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs127
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs342
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs47
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs260
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs (renamed from modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs)55
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs20
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs40
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/File.cs43
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs127
-rw-r--r--modules/mono/editor/bindings_generator.cpp1199
-rw-r--r--modules/mono/editor/bindings_generator.h85
-rw-r--r--modules/mono/editor/csharp_project.cpp206
-rw-r--r--modules/mono/editor/csharp_project.h7
-rw-r--r--modules/mono/editor/dotnet_solution.cpp140
-rw-r--r--modules/mono/editor/dotnet_solution.h63
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp429
-rw-r--r--modules/mono/editor/editor_internal_calls.h (renamed from modules/mono/editor/monodevelop_instance.h)30
-rw-r--r--modules/mono/editor/godotsharp_builds.cpp602
-rw-r--r--modules/mono/editor/godotsharp_builds.h103
-rw-r--r--modules/mono/editor/godotsharp_editor.cpp581
-rw-r--r--modules/mono/editor/godotsharp_editor.h134
-rw-r--r--modules/mono/editor/godotsharp_export.cpp194
-rw-r--r--modules/mono/editor/godotsharp_export.h28
-rw-r--r--modules/mono/editor/mono_bottom_panel.cpp525
-rw-r--r--modules/mono/editor/mono_bottom_panel.h150
-rw-r--r--modules/mono/editor/mono_build_info.cpp62
-rw-r--r--modules/mono/glue/Managed/.gitignore2
-rw-r--r--modules/mono/glue/Managed/Files/AABB.cs15
-rw-r--r--modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs16
-rw-r--r--modules/mono/glue/Managed/Files/Basis.cs73
-rw-r--r--modules/mono/glue/Managed/Files/Color.cs16
-rw-r--r--modules/mono/glue/Managed/Files/DynamicObject.cs2
-rw-r--r--modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs4
-rw-r--r--modules/mono/glue/Managed/Files/GD.cs15
-rw-r--r--modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs8
-rw-r--r--modules/mono/glue/Managed/Files/MarshalUtils.cs154
-rw-r--r--modules/mono/glue/Managed/Files/Mathf.cs52
-rw-r--r--modules/mono/glue/Managed/Files/MathfEx.cs14
-rw-r--r--modules/mono/glue/Managed/Files/Plane.cs2
-rw-r--r--modules/mono/glue/Managed/Files/Quat.cs2
-rw-r--r--modules/mono/glue/Managed/Files/StringExtensions.cs9
-rw-r--r--modules/mono/glue/Managed/Files/Transform2D.cs1
-rw-r--r--modules/mono/glue/Managed/Files/Vector2.cs34
-rw-r--r--modules/mono/glue/Managed/Files/Vector3.cs31
-rw-r--r--modules/mono/glue/Managed/IgnoredFiles/Node.cs5
-rw-r--r--modules/mono/glue/base_object_glue.cpp15
-rw-r--r--modules/mono/glue/collections_glue.cpp12
-rw-r--r--modules/mono/glue/gd_glue.cpp2
-rw-r--r--modules/mono/glue/gd_glue.h2
-rw-r--r--modules/mono/glue/string_glue.cpp12
-rw-r--r--modules/mono/godotsharp_defs.h3
-rw-r--r--modules/mono/godotsharp_dirs.cpp52
-rw-r--r--modules/mono/godotsharp_dirs.h1
-rw-r--r--modules/mono/mono_gc_handle.h2
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp273
-rw-r--r--modules/mono/mono_gd/gd_mono.h29
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.cpp49
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.h2
-rw-r--r--modules/mono/mono_gd/gd_mono_class.cpp17
-rw-r--r--modules/mono/mono_gd/gd_mono_class.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_field.cpp74
-rw-r--r--modules/mono/mono_gd/gd_mono_field.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_internals.cpp9
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp24
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.cpp256
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.h21
-rw-r--r--modules/mono/mono_gd/gd_mono_method.cpp6
-rw-r--r--modules/mono/mono_gd/gd_mono_method.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_property.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_property.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp243
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h88
-rw-r--r--modules/mono/mono_gd/i_mono_class_member.h6
-rw-r--r--modules/mono/signal_awaiter_utils.cpp4
-rw-r--r--modules/mono/signal_awaiter_utils.h2
-rw-r--r--modules/mono/utils/android_utils.cpp (renamed from modules/mono/editor/monodevelop_instance.cpp)71
-rw-r--r--modules/mono/utils/android_utils.h (renamed from modules/mono/editor/mono_build_info.h)31
-rw-r--r--modules/mono/utils/path_utils.cpp127
-rw-r--r--modules/mono/utils/path_utils.h32
-rw-r--r--modules/mono/utils/string_utils.cpp54
-rw-r--r--modules/mono/utils/string_utils.h13
-rw-r--r--modules/mono/utils/thread_local.h2
133 files changed, 7798 insertions, 4982 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index 341d57f3e4..cc60e64a11 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -1,5 +1,8 @@
#!/usr/bin/env python
+import build_scripts.tls_configure as tls_configure
+import build_scripts.mono_configure as mono_configure
+
Import('env')
Import('env_modules')
@@ -20,38 +23,42 @@ if env['tools']:
'glue/cs_glue_version.gen.h'
)
-vars = Variables()
-vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True))
-vars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False))
-vars.Update(env_mono)
-
# Glue sources
if env_mono['mono_glue']:
env_mono.Append(CPPDEFINES=['MONO_GLUE_ENABLED'])
import os.path
if not os.path.isfile('glue/mono_glue.gen.cpp'):
- raise RuntimeError('Missing mono glue sources. Did you forget to generate them?')
+ raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
if env_mono['tools'] or env_mono['target'] != 'release':
env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD'])
# Configure Thread Local Storage
-import build_scripts.tls_configure as tls_configure
-
conf = Configure(env_mono)
tls_configure.configure(conf)
env_mono = conf.Finish()
# Configure Mono
-import build_scripts.mono_configure as mono_configure
-
mono_configure.configure(env, env_mono)
-# Build GodotSharpTools
+# Build Godot API solution
+
+if env_mono['tools'] and env_mono['mono_glue']:
+ import build_scripts.api_solution_build as api_solution_build
+ api_solution_build.build(env_mono)
-import build_scripts.godotsharptools_build as godotsharptools_build
+# Build GodotTools
-godotsharptools_build.build(env_mono)
+if env_mono['tools']:
+ import build_scripts.godot_tools_build as godot_tools_build
+ if env_mono['mono_glue']:
+ godot_tools_build.build(env_mono)
+ else:
+ # Building without the glue sources so the Godot API solution may be missing.
+ # GodotTools depends on the Godot API solution. As such, we will only build
+ # GodotTools.ProjectEditor which doesn't depend on the Godot API solution and
+ # is required by the bindings generator in order to be able to generated it.
+ godot_tools_build.build_project_editor_only(env_mono)
diff --git a/modules/mono/__init__.py b/modules/mono/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/mono/__init__.py
diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py
new file mode 100644
index 0000000000..1fe00a3028
--- /dev/null
+++ b/modules/mono/build_scripts/api_solution_build.py
@@ -0,0 +1,66 @@
+# Build the Godot API solution
+
+import os
+
+from SCons.Script import Dir
+
+
+def build_api_solution(source, target, env):
+ # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
+
+ module_dir = env['module_dir']
+
+ solution_path = os.path.join(module_dir, 'glue/Managed/Generated/GodotSharp.sln')
+
+ if not os.path.isfile(solution_path):
+ raise RuntimeError("Godot API solution not found. Did you forget to run '--generate-mono-glue'?")
+
+ build_config = env['solution_build_config']
+
+ extra_msbuild_args = ['/p:NoWarn=1591'] # Ignore missing documentation warnings
+
+ from .solution_builder import build_solution
+ build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args)
+
+ # Copy targets
+
+ core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharp', 'bin', build_config))
+ editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharpEditor', 'bin', build_config))
+
+ dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
+
+ if not os.path.isdir(dst_dir):
+ assert not os.path.isfile(dst_dir)
+ os.makedirs(dst_dir)
+
+ def copy_target(target_path):
+ from shutil import copy
+ filename = os.path.basename(target_path)
+
+ src_path = os.path.join(core_src_dir, filename)
+ if not os.path.isfile(src_path):
+ src_path = os.path.join(editor_src_dir, filename)
+
+ copy(src_path, target_path)
+
+ for scons_target in target:
+ copy_target(str(scons_target))
+
+
+def build(env_mono):
+ assert env_mono['tools']
+
+ target_filenames = [
+ 'GodotSharp.dll', 'GodotSharp.pdb', 'GodotSharp.xml',
+ 'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml'
+ ]
+
+ for build_config in ['Debug', 'Release']:
+ output_dir = Dir('#bin').abspath
+ editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', build_config)
+
+ targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]
+
+ cmd = env_mono.CommandNoCache(targets, [], build_api_solution,
+ module_dir=os.getcwd(), solution_build_config=build_config)
+ env_mono.AlwaysBuild(cmd)
diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py
new file mode 100644
index 0000000000..f66ffdb573
--- /dev/null
+++ b/modules/mono/build_scripts/godot_tools_build.py
@@ -0,0 +1,108 @@
+# Build GodotTools solution
+
+import os
+
+from SCons.Script import Dir
+
+
+def build_godot_tools(source, target, env):
+ # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
+
+ module_dir = env['module_dir']
+
+ solution_path = os.path.join(module_dir, 'editor/GodotTools/GodotTools.sln')
+ build_config = 'Debug' if env['target'] == 'debug' else 'Release'
+
+ from . solution_builder import build_solution, nuget_restore
+ nuget_restore(env, solution_path)
+ build_solution(env, solution_path, build_config)
+
+ # Copy targets
+
+ solution_dir = os.path.abspath(os.path.join(solution_path, os.pardir))
+
+ src_dir = os.path.join(solution_dir, 'GodotTools', 'bin', build_config)
+ dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
+
+ if not os.path.isdir(dst_dir):
+ assert not os.path.isfile(dst_dir)
+ os.makedirs(dst_dir)
+
+ def copy_target(target_path):
+ from shutil import copy
+ filename = os.path.basename(target_path)
+ copy(os.path.join(src_dir, filename), target_path)
+
+ for scons_target in target:
+ copy_target(str(scons_target))
+
+
+def build_godot_tools_project_editor(source, target, env):
+ # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
+
+ module_dir = env['module_dir']
+
+ project_name = 'GodotTools.ProjectEditor'
+
+ csproj_dir = os.path.join(module_dir, 'editor/GodotTools', project_name)
+ csproj_path = os.path.join(csproj_dir, project_name + '.csproj')
+ build_config = 'Debug' if env['target'] == 'debug' else 'Release'
+
+ from . solution_builder import build_solution, nuget_restore
+
+ # Make sure to restore NuGet packages in the project directory for the project to find it
+ nuget_restore(env, os.path.join(csproj_dir, 'packages.config'), '-PackagesDirectory',
+ os.path.join(csproj_dir, 'packages'))
+
+ build_solution(env, csproj_path, build_config)
+
+ # Copy targets
+
+ src_dir = os.path.join(csproj_dir, 'bin', build_config)
+ dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
+
+ if not os.path.isdir(dst_dir):
+ assert not os.path.isfile(dst_dir)
+ os.makedirs(dst_dir)
+
+ def copy_target(target_path):
+ from shutil import copy
+ filename = os.path.basename(target_path)
+ copy(os.path.join(src_dir, filename), target_path)
+
+ for scons_target in target:
+ copy_target(str(scons_target))
+
+
+def build(env_mono):
+ assert env_mono['tools']
+
+ output_dir = Dir('#bin').abspath
+ editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
+ editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', 'Debug')
+
+ source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll']
+ sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames]
+
+ target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll']
+
+ if env_mono['target'] == 'debug':
+ target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'GodotTools.Core.dll']
+
+ targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
+
+ cmd = env_mono.CommandNoCache(targets, sources, build_godot_tools, module_dir=os.getcwd())
+ env_mono.AlwaysBuild(cmd)
+
+
+def build_project_editor_only(env_mono):
+ assert env_mono['tools']
+
+ output_dir = Dir('#bin').abspath
+ editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
+
+ target_filenames = ['GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll']
+ targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
+
+ cmd = env_mono.CommandNoCache(targets, [], build_godot_tools_project_editor, module_dir=os.getcwd())
+ env_mono.AlwaysBuild(cmd)
diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py
new file mode 100644
index 0000000000..cd9210897d
--- /dev/null
+++ b/modules/mono/build_scripts/make_android_mono_config.py
@@ -0,0 +1,69 @@
+
+def generate_compressed_config(config_src, output_dir):
+ import os.path
+ from compat import byte_to_str
+
+ # Header file
+ with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header:
+ header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */
+#ifndef ANDROID_MONO_CONFIG_GEN_H
+#define ANDROID_MONO_CONFIG_GEN_H
+
+#ifdef ANDROID_ENABLED
+
+#include "core/ustring.h"
+
+String get_godot_android_mono_config();
+
+#endif // ANDROID_ENABLED
+
+#endif // ANDROID_MONO_CONFIG_GEN_H
+''')
+
+ # Source file
+ with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp:
+ with open(config_src, 'rb') as f:
+ buf = f.read()
+ decompr_size = len(buf)
+ import zlib
+ buf = zlib.compress(buf)
+ compr_size = len(buf)
+
+ bytes_seq_str = ''
+ for i, buf_idx in enumerate(range(compr_size)):
+ if i > 0:
+ bytes_seq_str += ', '
+ bytes_seq_str += byte_to_str(buf[buf_idx])
+
+ cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */
+#include "android_mono_config.gen.h"
+
+#ifdef ANDROID_ENABLED
+
+#include "core/io/compression.h"
+#include "core/pool_vector.h"
+
+namespace {
+
+// config
+static const int config_compressed_size = %d;
+static const int config_uncompressed_size = %d;
+static const unsigned char config_compressed_data[] = { %s };
+
+} // namespace
+
+String get_godot_android_mono_config() {
+ PoolVector<uint8_t> data;
+ data.resize(config_uncompressed_size);
+ PoolVector<uint8_t>::Write w = data.write();
+ Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data,
+ config_compressed_size, Compression::MODE_DEFLATE);
+ String s;
+ if (s.parse_utf8((const char *)w.ptr(), data.size())) {
+ ERR_FAIL_V(String());
+ }
+ return s;
+}
+
+#endif // ANDROID_ENABLED
+''' % (compr_size, decompr_size, bytes_seq_str))
diff --git a/modules/mono/build_scripts/make_cs_compressed_header.py b/modules/mono/build_scripts/make_cs_compressed_header.py
index 1f9177cef8..ed49db5bb2 100644
--- a/modules/mono/build_scripts/make_cs_compressed_header.py
+++ b/modules/mono/build_scripts/make_cs_compressed_header.py
@@ -23,30 +23,31 @@ def generate_header(src, dst, version_dst):
latest_mtime = mtime if mtime > latest_mtime else latest_mtime
with open(filepath, 'rb') as f:
buf = f.read()
- decomp_size = len(buf)
+ decompr_size = len(buf)
import zlib
buf = zlib.compress(buf)
+ compr_size = len(buf)
name = str(cs_file_count)
header.write('\n')
header.write('// ' + filepath_src_rel + '\n')
- header.write('static const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n')
- header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n')
+ header.write('static const int _cs_' + name + '_compressed_size = ' + str(compr_size) + ';\n')
+ header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decompr_size) + ';\n')
header.write('static const unsigned char _cs_' + name + '_compressed[] = { ')
- for i, buf_idx in enumerate(range(len(buf))):
+ for i, buf_idx in enumerate(range(compr_size)):
if i > 0:
header.write(', ')
header.write(byte_to_str(buf[buf_idx]))
+ header.write(' };\n')
inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \
- 'CompressedFile(_cs_' + name + '_compressed_size, ' \
+ 'GodotCsCompressedFile(_cs_' + name + '_compressed_size, ' \
'_cs_' + name + '_uncompressed_size, ' \
'_cs_' + name + '_compressed));\n'
- header.write(' };\n')
- header.write('\nstruct CompressedFile\n' '{\n'
+ header.write('\nstruct GodotCsCompressedFile\n' '{\n'
'\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n'
- '\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
+ '\n\tGodotCsCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
'\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n'
- '\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n'
- '\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n'
+ '\t\tdata = p_data;\n' '\t}\n' '\n\tGodotCsCompressedFile() {}\n' '};\n'
+ '\nvoid get_compressed_files(Map<String, GodotCsCompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n'
)
header.write('\n#endif // TOOLS_ENABLED\n')
header.write('\n#endif // CS_COMPRESSED_H\n')
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index 160580e116..9f0eb58896 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -1,15 +1,28 @@
-import imp
import os
+import os.path
import sys
import subprocess
-from distutils.version import LooseVersion
-from SCons.Script import BoolVariable, Dir, Environment, Variables
+from SCons.Script import Dir, Environment
if os.name == 'nt':
from . import mono_reg_utils as monoreg
+android_arch_dirs = {
+ 'armv7': 'armeabi-v7a',
+ 'arm64v8': 'arm64-v8a',
+ 'x86': 'x86',
+ 'x86_64': 'x86_64'
+}
+
+
+def get_android_out_dir(env):
+ return os.path.join(Dir('#platform/android/java/libs').abspath,
+ 'release' if env['target'] == 'release' else 'debug',
+ android_arch_dirs[env['android_arch']])
+
+
def find_file_in_dir(directory, files, prefix='', extension=''):
if not extension.startswith('.'):
extension = '.' + extension
@@ -20,57 +33,64 @@ def find_file_in_dir(directory, files, prefix='', extension=''):
def copy_file(src_dir, dst_dir, name):
- from shutil import copyfile
+ from shutil import copy
- src_path = os.path.join(src_dir, name)
- dst_path = os.path.join(dst_dir, name)
+ src_path = os.path.join(Dir(src_dir).abspath, name)
+ dst_dir = Dir(dst_dir).abspath
if not os.path.isdir(dst_dir):
- os.mkdir(dst_dir)
+ os.makedirs(dst_dir)
- copyfile(src_path, dst_path)
+ copy(src_path, dst_dir)
def configure(env, env_mono):
- envvars = Variables()
- envvars.Add(BoolVariable('mono_static', 'Statically link mono', False))
- envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False))
- envvars.Update(env)
-
bits = env['bits']
+ is_android = env['platform'] == 'android'
tools_enabled = env['tools']
mono_static = env['mono_static']
copy_mono_root = env['copy_mono_root']
+ mono_prefix = env['mono_prefix']
+
mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0']
+ is_travis = os.environ.get('TRAVIS') == 'true'
+
+ if is_travis:
+ # Travis CI may have a Mono version lower than 5.12
+ env_mono.Append(CPPDEFINES=['NO_PENDING_EXCEPTIONS'])
+
+ if is_android and not env['android_arch'] in android_arch_dirs:
+ raise RuntimeError('This module does not support for the specified \'android_arch\': ' + env['android_arch'])
+
+ if is_android and tools_enabled:
+ # TODO: Implement this. We have to add the data directory to the apk, concretely the Api and Tools folders.
+ raise RuntimeError('This module does not currently support building for android with tools enabled')
+
+ if is_android and mono_static:
+ # When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0
+ raise RuntimeError('Linking Mono statically is not currently supported on Android')
+
+ if (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix:
+ print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead")
+
if env['platform'] == 'windows':
- mono_root = ''
+ mono_root = mono_prefix
- if bits == '32':
- if os.getenv('MONO32_PREFIX'):
- mono_root = os.getenv('MONO32_PREFIX')
- elif os.name == 'nt':
- mono_root = monoreg.find_mono_root_dir(bits)
- else:
- if os.getenv('MONO64_PREFIX'):
- mono_root = os.getenv('MONO64_PREFIX')
- elif os.name == 'nt':
- mono_root = monoreg.find_mono_root_dir(bits)
+ if not mono_root and os.name == 'nt':
+ mono_root = monoreg.find_mono_root_dir(bits)
if not mono_root:
- raise RuntimeError('Mono installation directory not found')
+ raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter")
print('Found Mono root directory: ' + mono_root)
- mono_version = mono_root_try_find_mono_version(mono_root)
- configure_for_mono_version(env_mono, mono_version)
-
mono_lib_path = os.path.join(mono_root, 'lib')
env.Append(LIBPATH=mono_lib_path)
- env_mono.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
+ env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
if mono_static:
lib_suffix = Environment()['LIBSUFFIX']
@@ -113,21 +133,18 @@ def configure(env, env_mono):
if not mono_dll_name:
raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path)
- copy_file(mono_bin_path, 'bin', mono_dll_name + '.dll')
+ copy_file(mono_bin_path, '#bin', mono_dll_name + '.dll')
else:
is_apple = (sys.platform == 'darwin' or "osxcross" in env)
sharedlib_ext = '.dylib' if is_apple else '.so'
- mono_root = ''
+ mono_root = mono_prefix
mono_lib_path = ''
+ mono_so_name = ''
- if bits == '32':
- if os.getenv('MONO32_PREFIX'):
- mono_root = os.getenv('MONO32_PREFIX')
- else:
- if os.getenv('MONO64_PREFIX'):
- mono_root = os.getenv('MONO64_PREFIX')
+ if not mono_root and is_android:
+ raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter")
if not mono_root and is_apple:
# Try with some known directories under OSX
@@ -142,25 +159,23 @@ def configure(env, env_mono):
if not mono_root and mono_static:
mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext)
if not mono_root:
- raise RuntimeError('Building with mono_static=yes, but failed to find the mono prefix with pkg-config. Specify one manually')
+ raise RuntimeError("Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " + \
+ "specify one manually with the 'mono_prefix' SCons parameter")
if mono_root:
print('Found Mono root directory: ' + mono_root)
- mono_version = mono_root_try_find_mono_version(mono_root)
- configure_for_mono_version(env_mono, mono_version)
-
mono_lib_path = os.path.join(mono_root, 'lib')
env.Append(LIBPATH=mono_lib_path)
- env_mono.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
+ env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
mono_lib = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension='.a')
if not mono_lib:
raise RuntimeError('Could not find mono library in: ' + mono_lib_path)
- env_mono.Append(CPPFLAGS=['-D_REENTRANT'])
+ env_mono.Append(CPPDEFINES=['_REENTRANT'])
if mono_static:
mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a')
@@ -174,6 +189,8 @@ def configure(env, env_mono):
if is_apple:
env.Append(LIBS=['iconv', 'pthread'])
+ elif is_android:
+ pass # Nothing
else:
env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
@@ -183,22 +200,16 @@ def configure(env, env_mono):
if not mono_so_name:
raise RuntimeError('Could not find mono shared library in: ' + mono_lib_path)
- copy_file(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext)
+ copy_file(mono_lib_path, '#bin', 'lib' + mono_so_name + sharedlib_ext)
else:
assert not mono_static
# TODO: Add option to force using pkg-config
print('Mono root directory not found. Using pkg-config instead')
- mono_version = pkgconfig_try_find_mono_version()
- configure_for_mono_version(env_mono, mono_version)
-
env.ParseConfig('pkg-config monosgen-2 --libs')
env_mono.ParseConfig('pkg-config monosgen-2 --cflags')
- mono_lib_path = ''
- mono_so_name = ''
-
tmpenv = Environment()
tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
@@ -213,15 +224,25 @@ def configure(env, env_mono):
if not mono_so_name:
raise RuntimeError('Could not find mono shared library in: ' + str(tmpenv['LIBPATH']))
- copy_file(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext)
+ if not mono_static:
+ libs_output_dir = get_android_out_dir(env) if is_android else '#bin'
+ copy_file(mono_lib_path, libs_output_dir, 'lib' + mono_so_name + sharedlib_ext)
env.Append(LINKFLAGS='-rdynamic')
- if not tools_enabled:
+ if not tools_enabled and not is_android:
if not mono_root:
mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip()
make_template_dir(env, mono_root)
+ elif not tools_enabled and is_android:
+ # Compress Android Mono Config
+ from . import make_android_mono_config
+ config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config')
+ make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/')
+
+ # Copy the required shared libraries
+ copy_mono_shared_libs(env, mono_root, None)
if copy_mono_root:
if not mono_root:
@@ -241,7 +262,7 @@ def make_template_dir(env, mono_root):
template_dir_name = ''
- if platform in ['windows', 'osx', 'x11']:
+ if platform in ['windows', 'osx', 'x11', 'android']:
template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target)
else:
assert False
@@ -261,7 +282,7 @@ def make_template_dir(env, mono_root):
# Copy the required shared libraries
- copy_mono_shared_libs(mono_root, template_mono_root_dir, env['platform'])
+ copy_mono_shared_libs(env, mono_root, template_mono_root_dir)
def copy_mono_root_files(env, mono_root):
@@ -285,7 +306,7 @@ def copy_mono_root_files(env, mono_root):
# Copy the required shared libraries
- copy_mono_shared_libs(mono_root, editor_mono_root_dir, env['platform'])
+ copy_mono_shared_libs(env, mono_root, editor_mono_root_dir)
# Copy framework assemblies
@@ -332,42 +353,47 @@ def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform):
copy_tree(os.path.join(mono_etc_dir, '2.0'), os.path.join(target_mono_config_dir, '2.0'))
copy_tree(os.path.join(mono_etc_dir, '4.0'), os.path.join(target_mono_config_dir, '4.0'))
copy_tree(os.path.join(mono_etc_dir, '4.5'), os.path.join(target_mono_config_dir, '4.5'))
- copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig'))
+ if os.path.isdir(os.path.join(mono_etc_dir, 'mconfig')):
+ copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig'))
for file in glob(os.path.join(mono_etc_dir, '*')):
if os.path.isfile(file):
copy(file, target_mono_config_dir)
-def copy_mono_shared_libs(mono_root, target_mono_root_dir, platform):
+def copy_mono_shared_libs(env, mono_root, target_mono_root_dir):
from shutil import copy
+ def copy_if_exists(src, dst):
+ if os.path.isfile(src):
+ copy(src, dst)
+
+ platform = env['platform']
+
if platform == 'windows':
target_mono_bin_dir = os.path.join(target_mono_root_dir, 'bin')
if not os.path.isdir(target_mono_bin_dir):
os.makedirs(target_mono_bin_dir)
- copy(os.path.join(mono_root, 'bin', 'MonoPosixHelper.dll'), os.path.join(target_mono_bin_dir, 'MonoPosixHelper.dll'))
+ copy(os.path.join(mono_root, 'bin', 'MonoPosixHelper.dll'), target_mono_bin_dir)
else:
- target_mono_lib_dir = os.path.join(target_mono_root_dir, 'lib')
+ target_mono_lib_dir = get_android_out_dir(env) if platform == 'android' else os.path.join(target_mono_root_dir, 'lib')
if not os.path.isdir(target_mono_lib_dir):
os.makedirs(target_mono_lib_dir)
if platform == 'osx':
- copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.dylib'), os.path.join(target_mono_lib_dir, 'libMonoPosixHelper.dylib'))
- elif platform == 'x11':
- copy(os.path.join(mono_root, 'lib', 'libmono-btls-shared.so'), os.path.join(target_mono_lib_dir, 'libmono-btls-shared.so'))
- copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.so'), os.path.join(target_mono_lib_dir, 'libMonoPosixHelper.so'))
-
+ # TODO: Make sure nothing is missing
+ copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.dylib'), target_mono_lib_dir)
+ elif platform == 'x11' or platform == 'android':
+ lib_file_names = [lib_name + '.so' for lib_name in [
+ 'libmono-btls-shared', 'libmono-ee-interp', 'libmono-native', 'libMonoPosixHelper',
+ 'libmono-profiler-aot', 'libmono-profiler-coverage', 'libmono-profiler-log', 'libMonoSupportW'
+ ]]
-def configure_for_mono_version(env, mono_version):
- if mono_version is None:
- raise RuntimeError('Mono JIT compiler version not found')
- print('Found Mono JIT compiler version: ' + str(mono_version))
- if mono_version >= LooseVersion('5.12.0'):
- env.Append(CPPFLAGS=['-DHAS_PENDING_EXCEPTIONS'])
+ for lib_file_name in lib_file_names:
+ copy_if_exists(os.path.join(mono_root, 'lib', lib_file_name), target_mono_lib_dir)
def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
@@ -379,36 +405,3 @@ def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')):
return os.path.join(hint_dir, '..')
return ''
-
-
-def pkgconfig_try_find_mono_version():
- from compat import decode_utf8
-
- lines = subprocess.check_output(['pkg-config', 'monosgen-2', '--modversion']).splitlines()
- greater_version = None
- for line in lines:
- try:
- version = LooseVersion(decode_utf8(line))
- if greater_version is None or version > greater_version:
- greater_version = version
- except ValueError:
- pass
- return greater_version
-
-
-def mono_root_try_find_mono_version(mono_root):
- from compat import decode_utf8
-
- mono_bin = os.path.join(mono_root, 'bin')
- if os.path.isfile(os.path.join(mono_bin, 'mono')):
- mono_binary = os.path.join(mono_bin, 'mono')
- elif os.path.isfile(os.path.join(mono_bin, 'mono.exe')):
- mono_binary = os.path.join(mono_bin, 'mono.exe')
- else:
- return None
- output = subprocess.check_output([mono_binary, '--version'])
- first_line = decode_utf8(output.splitlines()[0])
- try:
- return LooseVersion(first_line.split()[len('Mono JIT compiler version'.split())])
- except (ValueError, IndexError):
- return None
diff --git a/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff b/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff
new file mode 100644
index 0000000000..05f8dcadcc
--- /dev/null
+++ b/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff
@@ -0,0 +1,70 @@
+diff --git a/libgc/include/private/gcconfig.h b/libgc/include/private/gcconfig.h
+index e2bdf13ac3e..f962200ba4e 100644
+--- a/libgc/include/private/gcconfig.h
++++ b/libgc/include/private/gcconfig.h
+@@ -2255,6 +2255,14 @@
+ # define GETPAGESIZE() getpagesize()
+ # endif
+
++#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \
++ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \
++ || defined(ARM32) || defined(I386) /* but not x32 */)
++ /* tkill() exists only on arm32/mips(32)/x86. */
++ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */
++# define USE_TKILL_ON_ANDROID
++#endif
++
+ # if defined(SUNOS5) || defined(DRSNX) || defined(UTS4)
+ /* OS has SVR4 generic features. Probably others also qualify. */
+ # define SVR4
+diff --git a/libgc/pthread_stop_world.c b/libgc/pthread_stop_world.c
+index f93ce26b562..4a49a6d578c 100644
+--- a/libgc/pthread_stop_world.c
++++ b/libgc/pthread_stop_world.c
+@@ -336,7 +336,7 @@ void GC_push_all_stacks()
+ pthread_t GC_stopping_thread;
+ int GC_stopping_pid;
+
+-#ifdef HOST_ANDROID
++#ifdef USE_TKILL_ON_ANDROID
+ static
+ int android_thread_kill(pid_t tid, int sig)
+ {
+diff --git a/mono/metadata/threads.c b/mono/metadata/threads.c
+index ad9b8823f8f..3542b32b540 100644
+--- a/mono/metadata/threads.c
++++ b/mono/metadata/threads.c
+@@ -77,8 +77,12 @@ mono_native_thread_join_handle (HANDLE thread_handle, gboolean close_handle);
+ #include <zircon/syscalls.h>
+ #endif
+
+-#if defined(HOST_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64)
+-#define USE_TKILL_ON_ANDROID 1
++#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \
++ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \
++ || defined(ARM32) || defined(I386) /* but not x32 */)
++ /* tkill() exists only on arm32/mips(32)/x86. */
++ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */
++# define USE_TKILL_ON_ANDROID
+ #endif
+
+ #ifdef HOST_ANDROID
+diff --git a/mono/utils/mono-threads-posix.c b/mono/utils/mono-threads-posix.c
+index 3e4bf93de5f..79c9f731fe7 100644
+--- a/mono/utils/mono-threads-posix.c
++++ b/mono/utils/mono-threads-posix.c
+@@ -31,8 +31,12 @@
+
+ #include <errno.h>
+
+-#if defined(HOST_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64)
+-#define USE_TKILL_ON_ANDROID 1
++#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \
++ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \
++ || defined(ARM32) || defined(I386) /* but not x32 */)
++ /* tkill() exists only on arm32/mips(32)/x86. */
++ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */
++# define USE_TKILL_ON_ANDROID
+ #endif
+
+ #ifdef USE_TKILL_ON_ANDROID
diff --git a/modules/mono/build_scripts/godotsharptools_build.py b/modules/mono/build_scripts/solution_builder.py
index af3a5cb5c6..9f549a10ed 100644
--- a/modules/mono/build_scripts/godotsharptools_build.py
+++ b/modules/mono/build_scripts/solution_builder.py
@@ -1,9 +1,8 @@
-# Build GodotSharpTools solution
-
import os
-from SCons.Script import Builder, Dir
+
+verbose = False
def find_nuget_unix():
@@ -53,21 +52,9 @@ def find_nuget_windows(env):
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
- from . import mono_reg_utils as monoreg
+ from . mono_reg_utils import find_mono_root_dir
- mono_root = ''
- bits = env['bits']
-
- if bits == '32':
- if os.getenv('MONO32_PREFIX'):
- mono_root = os.getenv('MONO32_PREFIX')
- else:
- mono_root = monoreg.find_mono_root_dir(bits)
- else:
- if os.getenv('MONO64_PREFIX'):
- mono_root = os.getenv('MONO64_PREFIX')
- else:
- mono_root = monoreg.find_mono_root_dir(bits)
+ mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits'])
if mono_root:
mono_bin_dir = os.path.join(mono_root, 'bin')
@@ -114,21 +101,9 @@ def find_msbuild_unix(filename):
def find_msbuild_windows(env):
- from . import mono_reg_utils as monoreg
+ from . mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg
- mono_root = ''
- bits = env['bits']
-
- if bits == '32':
- if os.getenv('MONO32_PREFIX'):
- mono_root = os.getenv('MONO32_PREFIX')
- else:
- mono_root = monoreg.find_mono_root_dir(bits)
- else:
- if os.getenv('MONO64_PREFIX'):
- mono_root = os.getenv('MONO64_PREFIX')
- else:
- mono_root = monoreg.find_mono_root_dir(bits)
+ mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits'])
if not mono_root:
raise RuntimeError('Cannot find mono root directory')
@@ -148,7 +123,7 @@ def find_msbuild_windows(env):
}
return (msbuild_mono, framework_path, mono_msbuild_env)
- msbuild_tools_path = monoreg.find_msbuild_tools_path_reg()
+ msbuild_tools_path = find_msbuild_tools_path_reg()
if msbuild_tools_path:
return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), framework_path, {})
@@ -156,12 +131,46 @@ def find_msbuild_windows(env):
return None
-def mono_build_solution(source, target, env):
+def run_command(command, args, env_override=None, name=None):
+ def cmd_args_to_str(cmd_args):
+ return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args])
+
+ args = [command] + args
+
+ if name is None:
+ name = os.path.basename(command)
+
+ if verbose:
+ print("Running '%s': %s" % (name, cmd_args_to_str(args)))
+
import subprocess
- from shutil import copyfile
+ try:
+ if env_override is None:
+ subprocess.check_call(args)
+ else:
+ subprocess.check_call(args, env=env_override)
+ except subprocess.CalledProcessError as e:
+ 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')
- sln_path = os.path.abspath(str(source[0]))
- target_path = os.path.abspath(str(target[0]))
+
+def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
+ global verbose
+ verbose = env['verbose']
framework_path = ''
msbuild_env = os.environ.copy()
@@ -200,64 +209,10 @@ def mono_build_solution(source, target, env):
print('MSBuild path: ' + msbuild_path)
- # 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
-
- try:
- subprocess.check_call([nuget_path, 'restore', sln_path])
- except subprocess.CalledProcessError:
- raise RuntimeError('GodotSharpTools: NuGet restore failed')
-
# Build solution
- build_config = 'Release'
-
- msbuild_args = [
- msbuild_path,
- sln_path,
- '/p:Configuration=' + build_config,
- ]
-
- if framework_path:
- msbuild_args += ['/p:FrameworkPathOverride=' + framework_path]
-
- try:
- subprocess.check_call(msbuild_args, env=msbuild_env)
- except subprocess.CalledProcessError:
- raise RuntimeError('GodotSharpTools: Build failed')
-
- # Copy files
-
- src_dir = os.path.abspath(os.path.join(sln_path, os.pardir, 'bin', build_config))
- dst_dir = os.path.abspath(os.path.join(target_path, os.pardir))
- asm_file = 'GodotSharpTools.dll'
-
- if not os.path.isdir(dst_dir):
- if os.path.exists(dst_dir):
- raise RuntimeError('Target directory is a file')
- os.makedirs(dst_dir)
-
- copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file))
-
- # Dependencies
- copyfile(os.path.join(src_dir, "DotNet.Glob.dll"), os.path.join(dst_dir, "DotNet.Glob.dll"))
-
-def build(env_mono):
- if not env_mono['tools']:
- return
-
- output_dir = Dir('#bin').abspath
- editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
+ msbuild_args = [solution_path, '/p:Configuration=' + build_config]
+ msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] if framework_path else []
+ msbuild_args += extra_msbuild_args
- mono_sln_builder = Builder(action=mono_build_solution)
- env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder})
- env_mono.MonoBuildSolution(
- os.path.join(editor_tools_dir, 'GodotSharpTools.dll'),
- 'editor/GodotSharpTools/GodotSharpTools.sln'
- )
+ run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild')
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 3b2e96765e..9adf4ee6e5 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -8,6 +8,16 @@ def configure(env):
env.use_ptrcall = True
env.add_module_version_string('mono')
+ from SCons.Script import BoolVariable, PathVariable, Variables
+
+ envvars = Variables()
+ envvars.Add(PathVariable('mono_prefix', 'Path to the mono installation directory for the target platform and architecture', '', PathVariable.PathAccept))
+ envvars.Add(BoolVariable('mono_static', 'Statically link mono', False))
+ envvars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True))
+ envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False))
+ envvars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False))
+ envvars.Update(env)
+
def get_doc_classes():
return [
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 3c9644127c..7492816f18 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -42,9 +42,9 @@
#include "editor/bindings_generator.h"
#include "editor/csharp_project.h"
#include "editor/editor_node.h"
-#include "editor/godotsharp_editor.h"
#endif
+#include "editor/editor_internal_calls.h"
#include "godotsharp_dirs.h"
#include "mono_gd/gd_mono_class.h"
#include "mono_gd/gd_mono_marshal.h"
@@ -65,8 +65,8 @@ static bool _create_project_solution_if_needed() {
if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) {
// A solution does not yet exist, create a new one
- CRASH_COND(GodotSharpEditor::get_singleton() == NULL);
- return GodotSharpEditor::get_singleton()->call("_create_project_solution");
+ CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL);
+ return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution");
}
return true;
@@ -96,14 +96,6 @@ Error CSharpLanguage::execute_file(const String &p_path) {
return OK;
}
-#ifdef TOOLS_ENABLED
-void gdsharp_editor_init_callback() {
-
- EditorNode *editor = EditorNode::get_singleton();
- editor->add_child(memnew(GodotSharpEditor(editor)));
-}
-#endif
-
void CSharpLanguage::init() {
gdmono = memnew(GDMono);
@@ -114,14 +106,12 @@ void CSharpLanguage::init() {
#endif
#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
- if (gdmono->get_editor_tools_assembly() != NULL) {
- List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
- BindingsGenerator::handle_cmdline_args(cmdline_args);
- }
+ List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
+ BindingsGenerator::handle_cmdline_args(cmdline_args);
#endif
#ifdef TOOLS_ENABLED
- EditorNode::add_init_callback(&gdsharp_editor_init_callback);
+ EditorNode::add_init_callback(&_editor_init_callback);
GLOBAL_DEF("mono/export/include_scripts_content", false);
#endif
@@ -131,14 +121,6 @@ void CSharpLanguage::finish() {
finalizing = true;
-#ifdef TOOLS_ENABLED
- // Must be here, to avoid StringName leaks
- if (BindingsGenerator::singleton) {
- memdelete(BindingsGenerator::singleton);
- BindingsGenerator::singleton = NULL;
- }
-#endif
-
// Make sure all script binding gchandles are released before finalizing GDMono
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
@@ -672,7 +654,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
#ifdef TOOLS_ENABLED
- MonoReloadNode::get_singleton()->restart_reload_timer();
+ get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
#endif
#ifdef GD_MONO_HOT_RELOAD
@@ -690,19 +672,20 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
- String name = ProjectSettings::get_singleton()->get("application/config/name");
- if (name.empty()) {
- name = "UnnamedProject";
+ String appname = ProjectSettings::get_singleton()->get("application/config/name");
+ String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+ if (appname_safe.empty()) {
+ appname_safe = "UnnamedProject";
}
- name += ".dll";
+ appname_safe += ".dll";
if (proj_assembly) {
String proj_asm_path = proj_assembly->get_path();
if (!FileAccess::exists(proj_assembly->get_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(name);
+ proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe);
if (!FileAccess::exists(proj_asm_path))
return false; // No assembly to load
}
@@ -710,7 +693,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
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(name)))
+ if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe)))
return false; // No assembly to load
}
@@ -738,58 +721,93 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
SCOPED_MUTEX_LOCK(script_instances_mutex);
for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
- if (elem->self()->get_path().is_resource_file()) {
- // Cast to CSharpScript to avoid being erased by accident
- scripts.push_back(Ref<CSharpScript>(elem->self()));
- }
+ // Cast to CSharpScript to avoid being erased by accident
+ scripts.push_back(Ref<CSharpScript>(elem->self()));
}
}
List<Ref<CSharpScript> > to_reload;
+ // We need to keep reference instances alive during reloading
+ List<Ref<Reference> > ref_instances;
+
+ for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
+ CSharpScriptBinding &script_binding = E->value();
+ Reference *ref = Object::cast_to<Reference>(script_binding.owner);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
+ }
+ }
+
// As scripts are going to be reloaded, must proceed without locking here
scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
-
Ref<CSharpScript> &script = E->get();
to_reload.push_back(script);
+ if (script->get_path().empty()) {
+ script->tied_class_name_for_reload = script->script_class->get_name();
+ script->tied_class_namespace_for_reload = script->script_class->get_namespace();
+ }
+
// Script::instances are deleted during managed object disposal, which happens on domain finalize.
// Only placeholders are kept. Therefore we need to keep a copy before that happens.
for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
- script->pending_reload_instances.insert(F->get()->get_instance_id());
+ Object *obj = F->get();
+ script->pending_reload_instances.insert(obj->get_instance_id());
+
+ Reference *ref = Object::cast_to<Reference>(obj);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
+ }
}
#ifdef TOOLS_ENABLED
for (Set<PlaceHolderScriptInstance *>::Element *F = script->placeholders.front(); F; F = F->next()) {
- script->pending_reload_instances.insert(F->get()->get_owner()->get_instance_id());
+ Object *obj = F->get()->get_owner();
+ script->pending_reload_instances.insert(obj->get_instance_id());
+
+ Reference *ref = Object::cast_to<Reference>(obj);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
+ }
}
#endif
- // FIXME: What about references? Need to keep them alive if only managed code references them.
-
// Save state and remove script from instances
Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state;
- while (script->instances.front()) {
- Object *obj = script->instances.front()->get();
- // Save instance info
- CSharpScript::StateBackup state;
+ for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
+ Object *obj = F->get();
ERR_CONTINUE(!obj->get_script_instance());
- // TODO: Proper state backup (Not only variants, serialize managed state of scripts)
- obj->get_script_instance()->get_property_state(state.properties);
+ CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
- Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
- if (gchandle.is_valid())
- gchandle->release();
+ // Call OnBeforeSerialize
+ if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
+ obj->get_script_instance()->call_multilevel(string_names.on_before_serialize);
+
+ // Save instance info
+ CSharpScript::StateBackup state;
+
+ // TODO: Proper state backup (Not only variants, serialize managed state of scripts)
+ csi->get_properties_state_for_reloading(state.properties);
owners_map[obj->get_instance_id()] = state;
+ }
+ }
+
+ // After the state of all instances is saved, clear scripts and script instances
+ for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
+ Ref<CSharpScript> &script = E->get();
+
+ while (script->instances.front()) {
+ Object *obj = script->instances.front()->get();
obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
@@ -832,26 +850,80 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->pending_reload_state.erase(obj_id);
}
}
+
return;
}
+ List<Ref<CSharpScript> > to_reload_state;
+
for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
+ Ref<CSharpScript> script = E->get();
- Ref<CSharpScript> scr = E->get();
+ if (!script->get_path().empty()) {
#ifdef TOOLS_ENABLED
- scr->exports_invalidated = true;
+ script->exports_invalidated = true;
#endif
- scr->signals_invalidated = true;
- scr->reload(p_soft_reload);
- scr->update_exports();
+ script->signals_invalidated = true;
+
+ script->reload(p_soft_reload);
+ script->update_exports();
+
+ if (!script->valid) {
+ script->pending_reload_instances.clear();
+ continue;
+ }
+ } else {
+ const StringName &class_namespace = script->tied_class_namespace_for_reload;
+ const StringName &class_name = script->tied_class_name_for_reload;
+ GDMonoAssembly *project_assembly = gdmono->get_project_assembly();
+
+ // Search in project and tools assemblies first as those are the most likely to have the class
+ GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL);
+
+#ifdef TOOLS_ENABLED
+ if (!script_class) {
+ GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
+ script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL);
+ }
+#endif
+
+ if (!script_class) {
+ script_class = gdmono->get_class(class_namespace, class_name);
+ }
+
+ if (!script_class) {
+ // The class was removed, can't reload
+ script->pending_reload_instances.clear();
+ continue;
+ }
+
+ bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class);
+ if (!obj_type) {
+ // The class no longer inherits Godot.Object, can't reload
+ script->pending_reload_instances.clear();
+ continue;
+ }
+
+ GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class);
+
+ CSharpScript::initialize_for_managed_type(script, script_class, native);
+ }
+
+ String native_name = NATIVE_GDMONOCLASS_NAME(script->native);
{
- for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) {
+ for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
ObjectID obj_id = F->get();
Object *obj = ObjectDB::get_instance(obj_id);
if (!obj) {
- scr->pending_reload_state.erase(obj_id);
+ script->pending_reload_state.erase(obj_id);
+ continue;
+ }
+
+ if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) {
+ // No longer inherits the same compatible type, can't reload
+ script->pending_reload_state.erase(obj_id);
continue;
}
@@ -863,28 +935,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
// Non-placeholder script instances are removed in godot_icall_Object_Disposed.
CRASH_COND(!si->is_placeholder());
- if (scr->is_tool() || ScriptServer::is_scripting_enabled()) {
+ if (script->is_tool() || ScriptServer::is_scripting_enabled()) {
// Replace placeholder with a script instance
- CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
+ CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
// Backup placeholder script instance state before replacing it with a script instance
si->get_property_state(state_backup.properties);
- ScriptInstance *script_instance = scr->instance_create(obj);
+ ScriptInstance *script_instance = script->instance_create(obj);
if (script_instance) {
- scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
+ script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
obj->set_script_instance(script_instance);
}
-
- // TODO: Restore serialized state
-
- for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
- script_instance->set(G->get().first, G->get().second);
- }
-
- scr->pending_reload_state.erase(obj_id);
}
continue;
@@ -893,20 +957,42 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
CRASH_COND(si != NULL);
#endif
// Re-create script instance
+ obj->set_script(script.get_ref_ptr()); // will create the script instance as well
+ }
+ }
- obj->set_script(scr.get_ref_ptr()); // will create the script instance as well
+ to_reload_state.push_back(script);
+ }
- // TODO: Restore serialized state
+ for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) {
+ Ref<CSharpScript> script = E->get();
- for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
- obj->get_script_instance()->set(G->get().first, G->get().second);
- }
+ for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
+ ObjectID obj_id = F->get();
+ Object *obj = ObjectDB::get_instance(obj_id);
- scr->pending_reload_state.erase(obj_id);
+ if (!obj) {
+ script->pending_reload_state.erase(obj_id);
+ continue;
}
- scr->pending_reload_instances.clear();
+ ERR_CONTINUE(!obj->get_script_instance());
+
+ // TODO: Restore serialized state
+
+ CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
+
+ for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
+ obj->get_script_instance()->set(G->get().first, G->get().second);
+ }
+
+ // Call OnAfterDeserialization
+ CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
+ if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
+ obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
}
+
+ script->pending_reload_instances.clear();
}
#ifdef TOOLS_ENABLED
@@ -919,7 +1005,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
#endif
-void CSharpLanguage::project_assembly_loaded() {
+void CSharpLanguage::_load_scripts_metadata() {
scripts_metadata.clear();
@@ -953,6 +1039,7 @@ void CSharpLanguage::project_assembly_loaded() {
}
scripts_metadata = old_dict_var.operator Dictionary();
+ scripts_metadata_invalidated = false;
print_verbose("Successfully loaded scripts metadata");
} else {
@@ -970,12 +1057,12 @@ void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const
#ifdef TOOLS_ENABLED
Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) {
- return GodotSharpEditor::get_singleton()->open_in_external_editor(p_script, p_line, p_col);
+ return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col);
}
bool CSharpLanguage::overrides_external_editor() {
- return GodotSharpEditor::get_singleton()->overrides_external_editor();
+ return get_godotsharp_editor()->call("OverridesExternalEditor");
}
#endif
@@ -1024,12 +1111,42 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
}
}
-void CSharpLanguage::_uninitialize_script_bindings() {
+void CSharpLanguage::_on_scripts_domain_unloaded() {
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
script_binding.inited = false;
}
+
+ scripts_metadata_invalidated = true;
+}
+
+#ifdef TOOLS_ENABLED
+void CSharpLanguage::_editor_init_callback() {
+
+ register_editor_internal_calls();
+
+ // Initialize GodotSharpEditor
+
+ GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor");
+ CRASH_COND(editor_klass == NULL);
+
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr());
+ CRASH_COND(mono_object == NULL);
+
+ MonoException *exc = NULL;
+ GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc);
+ UNHANDLED_EXCEPTION(exc);
+
+ EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object));
+ CRASH_COND(godotsharp_editor == NULL);
+
+ // Enable it as a plugin
+ EditorNode::add_editor_plugin(godotsharp_editor);
+ godotsharp_editor->enable_plugin();
+
+ get_singleton()->godotsharp_editor = godotsharp_editor;
}
+#endif
void CSharpLanguage::set_language_index(int p_idx) {
@@ -1086,6 +1203,12 @@ CSharpLanguage::CSharpLanguage() {
#endif
lang_idx = -1;
+
+ scripts_metadata_invalidated = true;
+
+#ifdef TOOLS_ENABLED
+ godotsharp_editor = NULL;
+#endif
}
CSharpLanguage::~CSharpLanguage() {
@@ -1141,6 +1264,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
r_script_binding.type_name = type_name;
r_script_binding.wrapper_class = type_class; // cache
r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
+ r_script_binding.owner = p_object;
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(p_object);
@@ -1225,6 +1349,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ 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
// The reference count was increased after the managed side was the only one referencing our owner.
// This means the owner is being referenced again by the unmanaged side,
@@ -1249,14 +1376,17 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
CRASH_COND(!ref_owner);
#endif
- int refcount = ref_owner->reference_get_count();
-
void *data = p_object->get_script_instance_binding(get_language_index());
CRASH_COND(!data);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ int refcount = ref_owner->reference_get_count();
+
+ if (!script_binding.inited)
+ return refcount == 0;
+
if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// If owner owner is no longer referenced by the unmanaged side,
// the managed instance takes responsibility of deleting the owner when GCed.
@@ -1419,6 +1549,31 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
return false;
}
+void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) {
+
+ List<PropertyInfo> pinfo;
+ get_property_list(&pinfo);
+
+ for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
+ Pair<StringName, Variant> state_pair;
+ state_pair.first = E->get().name;
+
+ ManagedType managedType;
+
+ GDMonoField *field = script->script_class->get_field(state_pair.first);
+ if (!field)
+ continue; // Properties ignored. We get the property baking fields instead.
+
+ managedType = field->get_type();
+
+ if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it
+ if (get(state_pair.first, state_pair.second)) {
+ r_state.push_back(state_pair);
+ }
+ }
+ }
+}
+
void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) {
@@ -1616,17 +1771,18 @@ MonoObject *CSharpInstance::_internal_new_managed() {
ERR_FAIL_NULL_V(owner, NULL);
ERR_FAIL_COND_V(script.is_null(), NULL);
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr());
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
script = Ref<CSharpScript>();
- owner = NULL;
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);
+ owner = NULL;
+
ERR_EXPLAIN("Failed to allocate memory for the object");
ERR_FAIL_V(NULL);
}
@@ -1858,6 +2014,34 @@ void CSharpInstance::_call_notification(int p_notification) {
}
}
+String CSharpInstance::to_string(bool *r_valid) {
+ MonoObject *mono_object = get_mono_object();
+
+ if (mono_object == NULL) {
+ if (r_valid)
+ *r_valid = false;
+ return String();
+ }
+
+ MonoException *exc = NULL;
+ MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc);
+
+ if (exc) {
+ GDMonoUtils::set_pending_exception(exc);
+ if (r_valid)
+ *r_valid = false;
+ return String();
+ }
+
+ if (result == NULL) {
+ if (r_valid)
+ *r_valid = false;
+ return String();
+ }
+
+ return GDMonoMarshal::mono_string_to_godot(result);
+}
+
Ref<Script> CSharpInstance::get_script() const {
return script;
@@ -1914,7 +2098,16 @@ CSharpInstance::~CSharpInstance() {
CRASH_COND(data == NULL);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
- CRASH_COND(!script_binding.inited);
+
+ if (!script_binding.inited) {
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex());
+
+ if (!script_binding.inited) { // Other thread may have set it up
+ // Already had a binding that needs to be setup
+ CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, owner);
+ CRASH_COND(!script_binding.inited);
+ }
+ }
bool die = _unreference_owner_unsafe();
CRASH_COND(die == true); // The "instance binding" should be holding a reference
@@ -1956,6 +2149,52 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List
propnames.push_back(E->get());
}
}
+
+void CSharpScript::_update_member_info_no_exports() {
+
+ if (exports_invalidated) {
+ exports_invalidated = false;
+
+ member_info.clear();
+
+ GDMonoClass *top = script_class;
+
+ while (top && top != native) {
+ PropertyInfo prop_info;
+ bool exported;
+
+ const Vector<GDMonoField *> &fields = top->get_all_fields();
+
+ for (int i = fields.size() - 1; i >= 0; i--) {
+ GDMonoField *field = fields[i];
+
+ if (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) {
+ StringName member_name = field->get_name();
+
+ member_info[member_name] = prop_info;
+ exported_members_cache.push_front(prop_info);
+ exported_members_defval_cache[member_name] = Variant();
+ }
+ }
+
+ const Vector<GDMonoProperty *> &properties = top->get_all_properties();
+
+ for (int i = properties.size() - 1; i >= 0; i--) {
+ GDMonoProperty *property = properties[i];
+
+ if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) {
+ StringName member_name = property->get_name();
+
+ member_info[member_name] = prop_info;
+ exported_members_cache.push_front(prop_info);
+ exported_members_defval_cache[member_name] = Variant();
+ }
+ }
+
+ top = top->get_parent_class();
+ }
+ }
+}
#endif
bool CSharpScript::_update_exports() {
@@ -1982,7 +2221,7 @@ bool CSharpScript::_update_exports() {
// Here we create a temporary managed instance of the class to get the initial values
- MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
+ MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!tmp_object) {
ERR_PRINT("Failed to allocate temporary MonoObject");
@@ -2023,18 +2262,18 @@ bool CSharpScript::_update_exports() {
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
- if (_get_member_export(top, field, prop_info, exported)) {
- StringName name = field->get_name();
+ if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) {
+ StringName member_name = field->get_name();
if (exported) {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
- exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
+ exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
}
} else {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
}
}
}
@@ -2044,25 +2283,25 @@ bool CSharpScript::_update_exports() {
for (int i = properties.size() - 1; i >= 0; i--) {
GDMonoProperty *property = properties[i];
- if (_get_member_export(top, property, prop_info, exported)) {
- StringName name = property->get_name();
+ if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) {
+ StringName member_name = property->get_name();
if (exported) {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
MonoException *exc = NULL;
MonoObject *ret = property->get_value(tmp_object, &exc);
if (exc) {
- exported_members_defval_cache[name] = Variant();
+ exported_members_defval_cache[member_name] = Variant();
GDMonoUtils::debug_print_unhandled_exception(exc);
} else {
- exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret);
+ exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret);
}
}
} else {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
}
}
}
@@ -2171,17 +2410,19 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve
* 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(GDMonoClass *p_class, IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) {
+bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) {
- StringName name = p_member->get_name();
+ // Goddammit, C++. All I wanted was some nested functions.
+#define MEMBER_FULL_QUALIFIED_NAME(m_member) \
+ (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name())
if (p_member->is_static()) {
if (p_member->has_attribute(CACHED_CLASS(ExportAttribute)))
- ERR_PRINTS("Cannot export member because it is static: " + p_class->get_full_name() + "." + name.operator String());
+ ERR_PRINTS("Cannot export member because it is static: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
- if (member_info.has(name))
+ if (member_info.has(p_member->get_name()))
return false;
ManagedType type;
@@ -2194,39 +2435,68 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_
CRASH_NOW();
}
- GDMonoMarshal::ExportInfo export_info;
- Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type, &export_info);
-
- if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) {
- r_prop_info = PropertyInfo(variant_type, name.operator String(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
- r_exported = false;
- return true;
- }
+ bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute));
if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) {
GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
- if (!property->has_getter() || !property->has_setter()) {
- ERR_PRINTS("Cannot export property because it does not provide a getter or a setter: " + p_class->get_full_name() + "." + name.operator String());
+ if (!property->has_getter()) {
+ if (exported)
+ ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
+ return false;
+ }
+ if (!property->has_setter()) {
+ if (exported)
+ ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
}
+ Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
+
+ if (!p_inspect_export || !exported) {
+ r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = false;
+ return true;
+ }
+
MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
PropertyHint hint = PROPERTY_HINT_NONE;
String hint_string;
if (variant_type == Variant::NIL) {
- ERR_PRINTS("Unknown type of exported member: " + p_class->get_full_name() + "." + name.operator String());
+ ERR_PRINTS("Unknown exported member type: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
- } else if (variant_type == Variant::INT && type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(type.type_class->get_mono_ptr())) {
- // TODO: Move to ExportInfo?
- variant_type = Variant::INT;
- hint = PROPERTY_HINT_ENUM;
+ }
+
+ int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string);
+
+ if (hint_res == -1) {
+ ERR_EXPLAIN("Error while trying to determine information about the exported member: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
+ ERR_FAIL_V(false);
+ }
+
+ if (hint_res == 0) {
+ hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
+ hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
+ }
+
+ r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = true;
+
+ return true;
+
+#undef MEMBER_FULL_QUALIFIED_NAME
+}
- Vector<MonoClassField *> fields = type.type_class->get_enum_fields();
+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) {
- MonoType *enum_basetype = mono_class_enum_basetype(type.type_class->get_mono_ptr());
+ if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) {
+ r_hint = PROPERTY_HINT_ENUM;
+
+ Vector<MonoClassField *> fields = p_type.type_class->get_enum_fields();
+
+ MonoType *enum_basetype = mono_class_enum_basetype(p_type.type_class->get_mono_ptr());
String name_only_hint_string;
@@ -2239,12 +2509,12 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_
MonoClassField *field = fields[i];
if (i > 0) {
- hint_string += ",";
+ r_hint_string += ",";
name_only_hint_string += ",";
}
String enum_field_name = mono_field_get_name(field);
- hint_string += enum_field_name;
+ r_hint_string += enum_field_name;
name_only_hint_string += enum_field_name;
// TODO:
@@ -2254,53 +2524,73 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_
MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL);
if (val_obj == NULL) {
- ERR_PRINTS("Failed to get '" + enum_field_name + "' constant enum value of exported member: " +
- p_class->get_full_name() + "." + name.operator String());
- return false;
+ ERR_EXPLAIN("Failed to get '" + enum_field_name + "' constant enum value");
+ ERR_FAIL_V(-1);
}
bool r_error;
uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error);
if (r_error) {
- ERR_PRINTS("Failed to unbox '" + enum_field_name + "' constant enum value of exported member: " +
- p_class->get_full_name() + "." + name.operator String());
- return false;
+ ERR_EXPLAIN("Failed to unbox '" + enum_field_name + "' constant enum value");
+ ERR_FAIL_V(-1);
}
if (val != (unsigned int)i) {
uses_default_values = false;
}
- hint_string += ":";
- hint_string += String::num_uint64(val);
+ r_hint_string += ":";
+ r_hint_string += String::num_uint64(val);
}
if (uses_default_values) {
// If we use the format NAME:VAL, that's what the editor displays.
// That's annoying if the user is not using custom values for the enum constants.
// This may not be needed in the future if the editor is changed to not display values.
- hint_string = name_only_hint_string;
+ r_hint_string = name_only_hint_string;
}
- } else if (variant_type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(type.type_class)) {
- GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(type.type_class);
+ } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) {
+ GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class);
CRASH_COND(field_native_class == NULL);
- hint = PROPERTY_HINT_RESOURCE_TYPE;
- hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class);
- } else if (variant_type == Variant::ARRAY && export_info.array.element_type != Variant::NIL) {
- hint = PROPERTY_HINT_TYPE_STRING;
- hint_string = itos(export_info.array.element_type) + ":";
- } else if (variant_type == Variant::DICTIONARY && export_info.dictionary.key_type != Variant::NIL && export_info.dictionary.value_type != Variant::NIL) {
- // TODO: There is no hint for this yet
+ r_hint = PROPERTY_HINT_RESOURCE_TYPE;
+ r_hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class);
+ } else if (p_allow_generics && p_variant_type == Variant::ARRAY) {
+ // Nested arrays are not supported in the inspector
+
+ ManagedType elem_type;
+
+ if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type))
+ return 0;
+
+ Variant::Type elem_variant_type = GDMonoMarshal::managed_to_variant_type(elem_type);
+
+ PropertyHint elem_hint = PROPERTY_HINT_NONE;
+ String elem_hint_string;
+
+ if (elem_variant_type == Variant::NIL) {
+ ERR_EXPLAIN("Unknown array element type");
+ ERR_FAIL_V(-1);
+ }
+
+ int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string);
+
+ if (hint_res == -1) {
+ ERR_EXPLAIN("Error while trying to determine information about the array element type");
+ ERR_FAIL_V(-1);
+ }
+
+ // Format: type/hint:hint_string
+ r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string;
+ r_hint = PROPERTY_HINT_TYPE_STRING;
+
+ } else if (p_allow_generics && p_variant_type == Variant::DICTIONARY) {
+ // TODO: Dictionaries are not supported in the inspector
} else {
- hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
- hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
+ return 0;
}
- r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
- r_exported = true;
-
- return true;
+ return 1;
}
#endif
@@ -2388,33 +2678,58 @@ void CSharpScript::_bind_methods() {
Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) {
- // This method should not fail
+ // This method should not fail, only assertions allowed
- CRASH_COND(!p_class);
+ CRASH_COND(p_class == NULL);
- // TODO: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
+ // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
Ref<CSharpScript> script = memnew(CSharpScript);
- script->name = p_class->get_name();
- script->script_class = p_class;
- script->native = p_native;
+ initialize_for_managed_type(script, p_class, p_native);
+
+ return script;
+}
+
+void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) {
- CRASH_COND(script->native == NULL);
+ // This method should not fail, only assertions allowed
- GDMonoClass *base = script->script_class->get_parent_class();
+ CRASH_COND(p_class == NULL);
- if (base != script->native)
- script->base = base;
+ p_script->name = p_class->get_name();
+ p_script->script_class = p_class;
+ p_script->native = p_native;
+
+ CRASH_COND(p_script->native == NULL);
+
+ GDMonoClass *base = p_script->script_class->get_parent_class();
+
+ if (base != p_script->native)
+ p_script->base = base;
+
+ p_script->valid = true;
+ p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
+
+ if (!p_script->tool) {
+ GDMonoClass *nesting_class = p_script->script_class->get_nesting_class();
+ p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
+ }
+
+#if TOOLS_ENABLED
+ if (!p_script->tool) {
+ p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
+ }
+#endif
#ifdef DEBUG_ENABLED
// For debug builds, we must fetch from all native base methods as well.
// Native base methods must be fetched before the current class.
// Not needed if the script class itself is a native class.
- if (script->script_class != script->native) {
- GDMonoClass *native_top = script->native;
+ if (p_script->script_class != p_script->native) {
+ GDMonoClass *native_top = p_script->native;
while (native_top) {
- native_top->fetch_methods_with_godot_api_checks(script->native);
+ native_top->fetch_methods_with_godot_api_checks(p_script->native);
if (native_top == CACHED_CLASS(GodotObject))
break;
@@ -2424,18 +2739,19 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
}
#endif
- script->script_class->fetch_methods_with_godot_api_checks(script->native);
+ p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native);
// Need to fetch method from base classes as well
- GDMonoClass *top = script->script_class;
- while (top && top != script->native) {
- top->fetch_methods_with_godot_api_checks(script->native);
+ GDMonoClass *top = p_script->script_class;
+ while (top && top != p_script->native) {
+ top->fetch_methods_with_godot_api_checks(p_script->native);
top = top->get_parent_class();
}
- script->load_script_signals(script->script_class, script->native);
-
- return script;
+ p_script->load_script_signals(p_script->script_class, p_script->native);
+#ifdef TOOLS_ENABLED
+ p_script->_update_member_info_no_exports();
+#endif
}
bool CSharpScript::can_instance() const {
@@ -2443,7 +2759,8 @@ bool CSharpScript::can_instance() const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- if (get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
+ // Hack to lower the risk of attached scripts not being added to the C# project
+ if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
if (_create_project_solution_if_needed()) {
CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
"Compile",
@@ -2495,7 +2812,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
if (ctor == NULL) {
if (p_argcount == 0) {
- ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + get_path());
+ String path = get_path();
+ ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() +
+ "' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path));
}
ERR_EXPLAIN("Constructor not found");
@@ -2537,7 +2856,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
/* STEP 2, INITIALIZE AND CONSTRUCT */
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
@@ -2618,7 +2937,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
#endif
if (native) {
- String native_name = native->get_name();
+ String native_name = NATIVE_GDMONOCLASS_NAME(native);
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
if (ScriptDebugger::get_singleton()) {
CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
@@ -2629,7 +2948,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
}
Variant::CallError unchecked_error;
- return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error);
+ return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error);
}
PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) {
@@ -2744,11 +3063,22 @@ Error CSharpScript::reload(bool p_keep_state) {
if (script_class) {
#ifdef DEBUG_ENABLED
- print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path());
+ print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute));
+ if (!tool) {
+ GDMonoClass *nesting_class = script_class->get_nesting_class();
+ tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
+ }
+
+#if TOOLS_ENABLED
+ if (!tool) {
+ tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
+ }
+#endif
+
native = GDMonoUtils::get_class_native_base(script_class);
CRASH_COND(native == NULL);
@@ -2826,10 +3156,7 @@ void CSharpScript::update_exports() {
}
bool CSharpScript::has_script_signal(const StringName &p_signal) const {
- if (_signals.has(p_signal))
- return true;
-
- return false;
+ return _signals.has(p_signal);
}
void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
@@ -2949,7 +3276,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#endif
#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint() && mono_domain_get() == NULL) {
+ MonoDomain *domain = mono_domain_get();
+ if (Engine::get_singleton()->is_editor_hint() && domain == NULL) {
CRASH_COND(Thread::get_caller_id() == Thread::get_main_id());
@@ -2957,8 +3285,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
// because this may be called by one of the editor's worker threads.
// Attach this thread temporarily to reload the script.
- if (SCRIPTS_DOMAIN) {
- MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN);
+ if (domain) {
+ MonoThread *mono_thread = mono_thread_attach(domain);
CRASH_COND(mono_thread == NULL);
script->reload();
mono_thread_detach(mono_thread);
@@ -3058,5 +3386,7 @@ CSharpLanguage::StringNameCache::StringNameCache() {
_get_property_list = StaticCString::create("_get_property_list");
_notification = StaticCString::create("_notification");
_script_source = StaticCString::create("script/source");
+ on_before_serialize = StaticCString::create("OnBeforeSerialize");
+ on_after_deserialize = StaticCString::create("OnAfterDeserialize");
dotctor = StaticCString::create(".ctor");
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 050527d52b..eb168f344d 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -41,6 +41,10 @@
#include "mono_gd/gd_mono_header.h"
#include "mono_gd/gd_mono_internals.h"
+#ifdef TOOLS_ENABLED
+#include "editor/editor_plugin.h"
+#endif
+
class CSharpScript;
class CSharpInstance;
class CSharpLanguage;
@@ -63,7 +67,7 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
class CSharpScript : public Script {
- GDCLASS(CSharpScript, Script)
+ GDCLASS(CSharpScript, Script);
friend class CSharpInstance;
friend class CSharpLanguage;
@@ -92,6 +96,8 @@ class CSharpScript : public Script {
Set<ObjectID> pending_reload_instances;
Map<ObjectID, StateBackup> pending_reload_state;
+ StringName tied_class_name_for_reload;
+ StringName tied_class_namespace_for_reload;
#endif
String source;
@@ -115,6 +121,7 @@ class CSharpScript : public Script {
bool placeholder_fallback_enabled;
bool exports_invalidated;
void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames);
+ void _update_member_info_no_exports();
virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder);
#endif
@@ -127,7 +134,8 @@ class CSharpScript : public Script {
bool _update_exports();
#ifdef TOOLS_ENABLED
- bool _get_member_export(GDMonoClass *p_class, IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported);
+ bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
+ static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
#endif
CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error);
@@ -136,6 +144,7 @@ class CSharpScript : public Script {
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native);
+ static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native);
protected:
static void _bind_methods();
@@ -225,6 +234,8 @@ class CSharpInstance : public ScriptInstance {
MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
+ void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state);
+
public:
MonoObject *get_mono_object() const;
@@ -260,6 +271,8 @@ public:
virtual void notification(int p_notification);
void _call_notification(int p_notification);
+ virtual String to_string(bool *r_valid);
+
virtual Ref<Script> get_script() const;
virtual ScriptLanguage *get_language();
@@ -273,6 +286,7 @@ struct CSharpScriptBinding {
StringName type_name;
GDMonoClass *wrapper_class;
Ref<MonoGCHandle> gchandle;
+ Object *owner;
};
class CSharpLanguage : public ScriptLanguage {
@@ -302,6 +316,8 @@ class CSharpLanguage : public ScriptLanguage {
StringName _notification;
StringName _script_source;
StringName dotctor; // .ctor
+ StringName on_before_serialize; // OnBeforeSerialize
+ StringName on_after_deserialize; // OnAfterDeserialize
StringNameCache();
};
@@ -309,14 +325,23 @@ class CSharpLanguage : public ScriptLanguage {
int lang_idx;
Dictionary scripts_metadata;
+ bool scripts_metadata_invalidated;
// For debug_break and debug_break_parse
int _debug_parse_err_line;
String _debug_parse_err_file;
String _debug_error;
+ void _load_scripts_metadata();
+
friend class GDMono;
- void _uninitialize_script_bindings();
+ void _on_scripts_domain_unloaded();
+
+#ifdef TOOLS_ENABLED
+ EditorPlugin *godotsharp_editor;
+
+ static void _editor_init_callback();
+#endif
public:
StringNameCache string_names;
@@ -330,8 +355,12 @@ public:
_FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
+#ifdef TOOLS_ENABLED
+ _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
+#endif
+
static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
- static void release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle);
+ static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle);
bool debug_break(const String &p_error, bool p_allow_continue = true);
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
@@ -341,9 +370,15 @@ public:
void reload_assemblies(bool p_soft_reload);
#endif
- void project_assembly_loaded();
+ _FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() {
+ return scripts_metadata_invalidated ? Dictionary() : scripts_metadata;
+ }
- _FORCE_INLINE_ const Dictionary &get_scripts_metadata() { return scripts_metadata; }
+ _FORCE_INLINE_ const Dictionary &get_scripts_metadata() {
+ if (scripts_metadata_invalidated)
+ _load_scripts_metadata();
+ return scripts_metadata;
+ }
virtual String get_name() const;
@@ -368,7 +403,6 @@ public:
virtual bool supports_builtin_mode() const;
/* TODO? */ virtual int find_function(const String &p_function, const String &p_code) const { return -1; }
virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const;
- /* TODO? */ Error complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, String &r_call_hint) { return ERR_UNAVAILABLE; }
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) {}
@@ -429,7 +463,6 @@ public:
};
class ResourceFormatLoaderCSharpScript : public ResourceFormatLoader {
- GDCLASS(ResourceFormatLoaderCSharpScript, ResourceFormatLoader)
public:
virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
@@ -438,7 +471,6 @@ public:
};
class ResourceFormatSaverCSharpScript : public ResourceFormatSaver {
- GDCLASS(ResourceFormatSaverCSharpScript, 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;
diff --git a/modules/mono/doc_classes/@C#.xml b/modules/mono/doc_classes/@C#.xml
index a821713d0d..826c106d7e 100644
--- a/modules/mono/doc_classes/@C#.xml
+++ b/modules/mono/doc_classes/@C#.xml
@@ -6,8 +6,6 @@
</description>
<tutorials>
</tutorials>
- <demos>
- </demos>
<methods>
</methods>
<constants>
diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml
index 7f22388132..de2e246ea9 100644
--- a/modules/mono/doc_classes/CSharpScript.xml
+++ b/modules/mono/doc_classes/CSharpScript.xml
@@ -6,8 +6,6 @@
</description>
<tutorials>
</tutorials>
- <demos>
- </demos>
<methods>
<method name="new" qualifiers="vararg">
<return type="Object">
diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml
index 21835e639c..18556a84ba 100644
--- a/modules/mono/doc_classes/GodotSharp.xml
+++ b/modules/mono/doc_classes/GodotSharp.xml
@@ -6,8 +6,6 @@
</description>
<tutorials>
</tutorials>
- <demos>
- </demos>
<methods>
<method name="attach_thread">
<return type="void">
diff --git a/modules/mono/editor/GodotSharpTools/.gitignore b/modules/mono/editor/GodotSharpTools/.gitignore
deleted file mode 100644
index 296ad48834..0000000000
--- a/modules/mono/editor/GodotSharpTools/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# nuget packages
-packages \ No newline at end of file
diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs
deleted file mode 100644
index 967e3bcc19..0000000000
--- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs
+++ /dev/null
@@ -1,425 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Security;
-using Microsoft.Build.Framework;
-
-namespace GodotSharpTools.Build
-{
- public class BuildInstance : IDisposable
- {
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static string godot_icall_BuildInstance_get_MSBuildPath();
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir();
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows();
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static bool godot_icall_BuildInstance_get_PrintBuildOutput();
-
- private static string GetMSBuildPath()
- {
- string msbuildPath = godot_icall_BuildInstance_get_MSBuildPath();
-
- if (msbuildPath == null)
- throw new FileNotFoundException("Cannot find the MSBuild executable.");
-
- return msbuildPath;
- }
-
- private static string MonoWindowsBinDir
- {
- get
- {
- string monoWinBinDir = godot_icall_BuildInstance_get_MonoWindowsBinDir();
-
- if (monoWinBinDir == null)
- throw new FileNotFoundException("Cannot find the Windows Mono binaries directory.");
-
- return monoWinBinDir;
- }
- }
-
- private static bool UsingMonoMSBuildOnWindows
- {
- get
- {
- return godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows();
- }
- }
-
- private static bool PrintBuildOutput
- {
- get
- {
- return godot_icall_BuildInstance_get_PrintBuildOutput();
- }
- }
-
- private string solution;
- private string config;
-
- private Process process;
-
- private int exitCode;
- public int ExitCode { get { return exitCode; } }
-
- public bool IsRunning { get { return process != null && !process.HasExited; } }
-
- public BuildInstance(string solution, string config)
- {
- this.solution = solution;
- this.config = config;
- }
-
- public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
- {
- List<string> customPropertiesList = new List<string>();
-
- if (customProperties != null)
- customPropertiesList.AddRange(customProperties);
-
- string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
-
- ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
-
- bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput;
-
- if (!redirectOutput) // TODO: or if stdout verbose
- Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
-
- startInfo.RedirectStandardOutput = redirectOutput;
- startInfo.RedirectStandardError = redirectOutput;
- startInfo.UseShellExecute = false;
-
- if (UsingMonoMSBuildOnWindows)
- {
- // These environment variables are required for Mono's MSBuild to find the compilers.
- // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
- string monoWinBinDir = MonoWindowsBinDir;
- startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
- startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
- startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
- }
-
- // Needed when running from Developer Command Prompt for VS
- RemovePlatformVariable(startInfo.EnvironmentVariables);
-
- using (Process process = new Process())
- {
- process.StartInfo = startInfo;
-
- process.Start();
-
- if (redirectOutput)
- {
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
- }
-
- process.WaitForExit();
-
- exitCode = process.ExitCode;
- }
-
- return true;
- }
-
- public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
- {
- if (process != null)
- throw new InvalidOperationException("Already in use");
-
- List<string> customPropertiesList = new List<string>();
-
- if (customProperties != null)
- customPropertiesList.AddRange(customProperties);
-
- string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
-
- ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
-
- bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput;
-
- if (!redirectOutput) // TODO: or if stdout verbose
- Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
-
- startInfo.RedirectStandardOutput = redirectOutput;
- startInfo.RedirectStandardError = redirectOutput;
- startInfo.UseShellExecute = false;
-
- if (UsingMonoMSBuildOnWindows)
- {
- // These environment variables are required for Mono's MSBuild to find the compilers.
- // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
- string monoWinBinDir = MonoWindowsBinDir;
- startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
- startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
- startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
- }
-
- // Needed when running from Developer Command Prompt for VS
- RemovePlatformVariable(startInfo.EnvironmentVariables);
-
- process = new Process();
- process.StartInfo = startInfo;
- process.EnableRaisingEvents = true;
- process.Exited += new EventHandler(BuildProcess_Exited);
-
- process.Start();
-
- if (redirectOutput)
- {
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
- }
-
- return true;
- }
-
- private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties)
- {
- string arguments = string.Format(@"""{0}"" /v:normal /t:Build ""/p:{1}"" ""/l:{2},{3};{4}""",
- solution,
- "Configuration=" + config,
- typeof(GodotBuildLogger).FullName,
- loggerAssemblyPath,
- loggerOutputDir
- );
-
- foreach (string customProperty in customProperties)
- {
- arguments += " \"/p:" + customProperty + "\"";
- }
-
- return arguments;
- }
-
- private void RemovePlatformVariable(StringDictionary environmentVariables)
- {
- // EnvironmentVariables is case sensitive? Seriously?
-
- List<string> platformEnvironmentVariables = new List<string>();
-
- foreach (string env in environmentVariables.Keys)
- {
- if (env.ToUpper() == "PLATFORM")
- platformEnvironmentVariables.Add(env);
- }
-
- foreach (string env in platformEnvironmentVariables)
- environmentVariables.Remove(env);
- }
-
- private void BuildProcess_Exited(object sender, System.EventArgs e)
- {
- exitCode = process.ExitCode;
-
- godot_icall_BuildInstance_ExitCallback(solution, config, exitCode);
-
- Dispose();
- }
-
- private static bool IsDebugMSBuildRequested()
- {
- return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
- }
-
- public void Dispose()
- {
- if (process != null)
- {
- process.Dispose();
- process = null;
- }
- }
- }
-
- public class GodotBuildLogger : ILogger
- {
- public string Parameters { get; set; }
- public LoggerVerbosity Verbosity { get; set; }
-
- public void Initialize(IEventSource eventSource)
- {
- if (null == Parameters)
- throw new LoggerException("Log directory was not set.");
-
- string[] parameters = Parameters.Split(new[] { ';' });
-
- string logDir = parameters[0];
-
- if (String.IsNullOrEmpty(logDir))
- throw new LoggerException("Log directory was not set.");
-
- if (parameters.Length > 1)
- throw new LoggerException("Too many parameters passed.");
-
- string logFile = Path.Combine(logDir, "msbuild_log.txt");
- string issuesFile = Path.Combine(logDir, "msbuild_issues.csv");
-
- try
- {
- if (!Directory.Exists(logDir))
- Directory.CreateDirectory(logDir);
-
- this.logStreamWriter = new StreamWriter(logFile);
- this.issuesStreamWriter = new StreamWriter(issuesFile);
- }
- catch (Exception ex)
- {
- if
- (
- ex is UnauthorizedAccessException
- || ex is ArgumentNullException
- || ex is PathTooLongException
- || ex is DirectoryNotFoundException
- || ex is NotSupportedException
- || ex is ArgumentException
- || ex is SecurityException
- || ex is IOException
- )
- {
- throw new LoggerException("Failed to create log file: " + ex.Message);
- }
- else
- {
- // Unexpected failure
- throw;
- }
- }
-
- eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
- eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted);
- eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
- eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
- eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
- eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
- }
-
- void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
- {
- string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message);
-
- if (e.ProjectFile.Length > 0)
- line += string.Format(" [{0}]", e.ProjectFile);
-
- WriteLine(line);
-
- string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}",
- e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
- e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape());
- issuesStreamWriter.WriteLine(errorLine);
- }
-
- void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
- {
- string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile);
-
- if (e.ProjectFile != null && e.ProjectFile.Length > 0)
- line += string.Format(" [{0}]", e.ProjectFile);
-
- WriteLine(line);
-
- string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}",
- e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
- e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty);
- issuesStreamWriter.WriteLine(warningLine);
- }
-
- void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
- {
- // BuildMessageEventArgs adds Importance to BuildEventArgs
- // Let's take account of the verbosity setting we've been passed in deciding whether to log the message
- if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal))
- || (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal))
- || (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
- )
- {
- WriteLineWithSenderAndMessage(String.Empty, e);
- }
- }
-
- void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
- {
- // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
- // To keep this log clean, this logger will ignore these events.
- }
-
- void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
- {
- WriteLine(e.Message);
- indent++;
- }
-
- void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
- {
- indent--;
- WriteLine(e.Message);
- }
-
- /// <summary>
- /// Write a line to the log, adding the SenderName
- /// </summary>
- private void WriteLineWithSender(string line, BuildEventArgs e)
- {
- if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
- {
- // Well, if the sender name is MSBuild, let's leave it out for prettiness
- WriteLine(line);
- }
- else
- {
- WriteLine(e.SenderName + ": " + line);
- }
- }
-
- /// <summary>
- /// Write a line to the log, adding the SenderName and Message
- /// (these parameters are on all MSBuild event argument objects)
- /// </summary>
- private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
- {
- if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
- {
- // Well, if the sender name is MSBuild, let's leave it out for prettiness
- WriteLine(line + e.Message);
- }
- else
- {
- WriteLine(e.SenderName + ": " + line + e.Message);
- }
- }
-
- private void WriteLine(string line)
- {
- for (int i = indent; i > 0; i--)
- {
- logStreamWriter.Write("\t");
- }
- logStreamWriter.WriteLine(line);
- }
-
- public void Shutdown()
- {
- logStreamWriter.Close();
- issuesStreamWriter.Close();
- }
-
- public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
- {
- return this.Verbosity >= checkVerbosity;
- }
-
- private StreamWriter logStreamWriter;
- private StreamWriter issuesStreamWriter;
- private int indent;
- }
-}
diff --git a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs
deleted file mode 100644
index e45dd2025b..0000000000
--- a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.CompilerServices;
-
-namespace GodotSharpTools.Editor
-{
- public static class GodotSharpExport
- {
- public static void _ExportBegin(string[] features, bool debug, string path, int flags)
- {
- var featureSet = new HashSet<string>(features);
-
- if (PlatformHasTemplateDir(featureSet))
- {
- string templateDirName = "data.mono";
-
- if (featureSet.Contains("Windows"))
- {
- templateDirName += ".windows";
- templateDirName += featureSet.Contains("64") ? ".64" : ".32";
- }
- else if (featureSet.Contains("X11"))
- {
- templateDirName += ".x11";
- templateDirName += featureSet.Contains("64") ? ".64" : ".32";
- }
- else
- {
- throw new NotSupportedException("Target platform not supported");
- }
-
- templateDirName += debug ? ".release_debug" : ".release";
-
- string templateDirPath = Path.Combine(GetTemplatesDir(), templateDirName);
-
- if (!Directory.Exists(templateDirPath))
- throw new FileNotFoundException("Data template directory not found");
-
- string outputDir = new FileInfo(path).Directory.FullName;
-
- string outputDataDir = Path.Combine(outputDir, GetDataDirName());
-
- if (Directory.Exists(outputDataDir))
- Directory.Delete(outputDataDir, recursive: true); // Clean first
-
- Directory.CreateDirectory(outputDataDir);
-
- foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories))
- {
- Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1)));
- }
-
- foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories))
- {
- File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
- }
- }
- }
-
- public static bool PlatformHasTemplateDir(HashSet<string> featureSet)
- {
- // OSX export templates are contained in a zip, so we place
- // our custom template inside it and let Godot do the rest.
- return !featureSet.Contains("OSX");
- }
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- extern static string GetTemplatesDir();
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- extern static string GetDataDirName();
- }
-}
diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln
deleted file mode 100644
index 5f7d0e8a39..0000000000
--- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln
+++ /dev/null
@@ -1,17 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpTools", "GodotSharpTools.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
-EndGlobal
diff --git a/modules/mono/editor/GodotSharpTools/Utils/OS.cs b/modules/mono/editor/GodotSharpTools/Utils/OS.cs
deleted file mode 100644
index 148e954e77..0000000000
--- a/modules/mono/editor/GodotSharpTools/Utils/OS.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using System.Linq;
-using System.Runtime.CompilerServices;
-
-namespace GodotSharpTools.Utils
-{
- public static class OS
- {
- [MethodImpl(MethodImplOptions.InternalCall)]
- extern static string GetPlatformName();
-
- const string HaikuName = "Haiku";
- const string OSXName = "OSX";
- const string ServerName = "Server";
- const string UWPName = "UWP";
- const string WindowsName = "Windows";
- const string X11Name = "X11";
-
- public static bool IsHaiku()
- {
- return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
- }
-
- public static bool IsOSX()
- {
- return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
- }
-
- public static bool IsServer()
- {
- return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
- }
-
- public static bool IsUWP()
- {
- return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
- }
-
- public static bool IsWindows()
- {
- return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
- }
-
- public static bool IsX11()
- {
- return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
- }
-
- static bool? IsUnixCache = null;
- static readonly string[] UnixPlatforms = new string[] { HaikuName, OSXName, ServerName, X11Name };
-
- public static bool IsUnix()
- {
- if (IsUnixCache.HasValue)
- return IsUnixCache.Value;
-
- string osName = GetPlatformName();
- IsUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
- return IsUnixCache.Value;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/.gitignore b/modules/mono/editor/GodotTools/.gitignore
new file mode 100644
index 0000000000..48e2f914d8
--- /dev/null
+++ b/modules/mono/editor/GodotTools/.gitignore
@@ -0,0 +1,356 @@
+# Rider
+.idea/
+
+# Visual Studio Code
+.vscode/
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
new file mode 100644
index 0000000000..a0f6f1ff32
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
@@ -0,0 +1,186 @@
+using System;
+using System.IO;
+using System.Security;
+using Microsoft.Build.Framework;
+using GodotTools.Core;
+
+namespace GodotTools.BuildLogger
+{
+ public class GodotBuildLogger : ILogger
+ {
+ public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location);
+
+ public string Parameters { get; set; }
+ public LoggerVerbosity Verbosity { get; set; }
+
+ public void Initialize(IEventSource eventSource)
+ {
+ if (null == Parameters)
+ throw new LoggerException("Log directory was not set.");
+
+ var parameters = Parameters.Split(new[] {';'});
+
+ string logDir = parameters[0];
+
+ if (string.IsNullOrEmpty(logDir))
+ throw new LoggerException("Log directory was not set.");
+
+ if (parameters.Length > 1)
+ throw new LoggerException("Too many parameters passed.");
+
+ string logFile = Path.Combine(logDir, "msbuild_log.txt");
+ string issuesFile = Path.Combine(logDir, "msbuild_issues.csv");
+
+ try
+ {
+ if (!Directory.Exists(logDir))
+ Directory.CreateDirectory(logDir);
+
+ logStreamWriter = new StreamWriter(logFile);
+ issuesStreamWriter = new StreamWriter(issuesFile);
+ }
+ catch (Exception ex)
+ {
+ if (ex is UnauthorizedAccessException
+ || ex is ArgumentNullException
+ || ex is PathTooLongException
+ || ex is DirectoryNotFoundException
+ || ex is NotSupportedException
+ || ex is ArgumentException
+ || ex is SecurityException
+ || ex is IOException)
+ {
+ throw new LoggerException("Failed to create log file: " + ex.Message);
+ }
+ else
+ {
+ // Unexpected failure
+ throw;
+ }
+ }
+
+ eventSource.ProjectStarted += eventSource_ProjectStarted;
+ eventSource.TaskStarted += eventSource_TaskStarted;
+ eventSource.MessageRaised += eventSource_MessageRaised;
+ eventSource.WarningRaised += eventSource_WarningRaised;
+ eventSource.ErrorRaised += eventSource_ErrorRaised;
+ eventSource.ProjectFinished += eventSource_ProjectFinished;
+ }
+
+ void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
+ {
+ string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}";
+
+ if (e.ProjectFile.Length > 0)
+ line += $" [{e.ProjectFile}]";
+
+ WriteLine(line);
+
+ string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}";
+ issuesStreamWriter.WriteLine(errorLine);
+ }
+
+ void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
+ {
+ string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}";
+
+ if (!string.IsNullOrEmpty(e.ProjectFile))
+ line += $" [{e.ProjectFile}]";
+
+ WriteLine(line);
+
+ string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," +
+ $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}";
+ issuesStreamWriter.WriteLine(warningLine);
+ }
+
+ private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
+ {
+ // BuildMessageEventArgs adds Importance to BuildEventArgs
+ // Let's take account of the verbosity setting we've been passed in deciding whether to log the message
+ if (e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)
+ || e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)
+ || e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
+ {
+ WriteLineWithSenderAndMessage(string.Empty, e);
+ }
+ }
+
+ private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
+ {
+ // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
+ // To keep this log clean, this logger will ignore these events.
+ }
+
+ private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
+ {
+ WriteLine(e.Message);
+ indent++;
+ }
+
+ private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
+ {
+ indent--;
+ WriteLine(e.Message);
+ }
+
+ /// <summary>
+ /// Write a line to the log, adding the SenderName
+ /// </summary>
+ private void WriteLineWithSender(string line, BuildEventArgs e)
+ {
+ if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase))
+ {
+ // Well, if the sender name is MSBuild, let's leave it out for prettiness
+ WriteLine(line);
+ }
+ else
+ {
+ WriteLine(e.SenderName + ": " + line);
+ }
+ }
+
+ /// <summary>
+ /// Write a line to the log, adding the SenderName and Message
+ /// (these parameters are on all MSBuild event argument objects)
+ /// </summary>
+ private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
+ {
+ if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase))
+ {
+ // Well, if the sender name is MSBuild, let's leave it out for prettiness
+ WriteLine(line + e.Message);
+ }
+ else
+ {
+ WriteLine(e.SenderName + ": " + line + e.Message);
+ }
+ }
+
+ private void WriteLine(string line)
+ {
+ for (int i = indent; i > 0; i--)
+ {
+ logStreamWriter.Write("\t");
+ }
+
+ logStreamWriter.WriteLine(line);
+ }
+
+ public void Shutdown()
+ {
+ logStreamWriter.Close();
+ issuesStreamWriter.Close();
+ }
+
+ private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
+ {
+ return Verbosity >= checkVerbosity;
+ }
+
+ private StreamWriter logStreamWriter;
+ private StreamWriter issuesStreamWriter;
+ private int indent;
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
new file mode 100644
index 0000000000..f3ac353c0f
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
@@ -0,0 +1,59 @@
+<?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>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>GodotTools.BuildLogger</RootNamespace>
+ <AssemblyName>GodotTools.BuildLogger</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </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>
+ </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> \ No newline at end of file
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..8717c4901e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+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/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
new file mode 100644
index 0000000000..f36b40f87c
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <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.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ProcessExtensions.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="StringExtensions.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs
new file mode 100644
index 0000000000..43d40f2ad9
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GodotTools.Core
+{
+ public static class ProcessExtensions
+ {
+ public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ void ProcessExited(object sender, EventArgs e)
+ {
+ tcs.TrySetResult(true);
+ }
+
+ process.EnableRaisingEvents = true;
+ process.Exited += ProcessExited;
+
+ try
+ {
+ if (process.HasExited)
+ return;
+
+ using (cancellationToken.Register(() => tcs.TrySetCanceled()))
+ {
+ await tcs.Task;
+ }
+ }
+ finally
+ {
+ process.Exited -= ProcessExited;
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..699ae6e741
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+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/GodotSharpTools/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
index b0436d2f18..8cd7e76303 100644
--- a/modules/mono/editor/GodotSharpTools/StringExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
@@ -1,7 +1,8 @@
using System;
+using System.Collections.Generic;
using System.IO;
-namespace GodotSharpTools
+namespace GodotTools.Core
{
public static class StringExtensions
{
@@ -25,7 +26,7 @@ namespace GodotSharpTools
path = path.Replace('\\', '/');
- string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim();
@@ -37,18 +38,40 @@ namespace GodotSharpTools
public static bool IsAbsolutePath(this string path)
{
return path.StartsWith("/", StringComparison.Ordinal) ||
- path.StartsWith("\\", StringComparison.Ordinal) ||
- path.StartsWith(driveRoot, 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;
+ bool hasSpecialChar = value.IndexOfAny(new char[] {'\"', '\n', '\r', delimiter}) != -1;
if (hasSpecialChar)
return "\"" + value.Replace("\"", "\"\"") + "\"";
return value;
}
+
+ public static string ToSafeDirName(this string dirName, bool allowDirSeparator)
+ {
+ var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"};
+
+ if (allowDirSeparator)
+ {
+ // Directory separators are allowed, but disallow ".." to avoid going up the filesystem
+ invalidChars.Add("..");
+ }
+ else
+ {
+ invalidChars.Add("/");
+ }
+
+ string safeDirName = dirName.Replace("\\", "/").Trim();
+
+ foreach (string invalidChar in invalidChars)
+ safeDirName = safeDirName.Replace(invalidChar, "-");
+
+ return safeDirName;
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs
new file mode 100644
index 0000000000..345a472185
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs
@@ -0,0 +1,15 @@
+namespace GodotTools
+{
+ public static class ApiAssemblyNames
+ {
+ public const string SolutionName = "GodotSharp";
+ public const string Core = "GodotSharp";
+ public const string Editor = "GodotSharpEditor";
+ }
+
+ public enum ApiAssemblyType
+ {
+ Core,
+ Editor
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs
new file mode 100644
index 0000000000..bfae2afc13
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace GodotTools.ProjectEditor
+{
+ public static class ApiSolutionGenerator
+ {
+ public static void GenerateApiSolution(string solutionDir,
+ string coreProjDir, IEnumerable<string> coreCompileItems,
+ string editorProjDir, IEnumerable<string> editorCompileItems)
+ {
+ var solution = new DotNetSolution(ApiAssemblyNames.SolutionName);
+
+ solution.DirectoryPath = solutionDir;
+
+ // GodotSharp project
+
+ const string coreApiAssemblyName = ApiAssemblyNames.Core;
+
+ string coreGuid = ProjectGenerator.GenCoreApiProject(coreProjDir, coreCompileItems);
+
+ var coreProjInfo = new DotNetSolution.ProjectInfo
+ {
+ Guid = coreGuid,
+ PathRelativeToSolution = Path.Combine(coreApiAssemblyName, $"{coreApiAssemblyName}.csproj")
+ };
+ coreProjInfo.Configs.Add("Debug");
+ coreProjInfo.Configs.Add("Release");
+
+ solution.AddNewProject(coreApiAssemblyName, coreProjInfo);
+
+ // GodotSharpEditor project
+
+ const string editorApiAssemblyName = ApiAssemblyNames.Editor;
+
+ string editorGuid = ProjectGenerator.GenEditorApiProject(editorProjDir,
+ $"../{coreApiAssemblyName}/{coreApiAssemblyName}.csproj", editorCompileItems);
+
+ var editorProjInfo = new DotNetSolution.ProjectInfo();
+ editorProjInfo.Guid = editorGuid;
+ editorProjInfo.PathRelativeToSolution = Path.Combine(editorApiAssemblyName, $"{editorApiAssemblyName}.csproj");
+ editorProjInfo.Configs.Add("Debug");
+ editorProjInfo.Configs.Add("Release");
+
+ solution.AddNewProject(editorApiAssemblyName, editorProjInfo);
+
+ // Save solution
+
+ solution.Save();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs
new file mode 100644
index 0000000000..76cb249acf
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs
@@ -0,0 +1,122 @@
+using GodotTools.Core;
+using System.Collections.Generic;
+using System.IO;
+
+namespace GodotTools.ProjectEditor
+{
+ public class DotNetSolution
+ {
+ private string directoryPath;
+ private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>();
+
+ public string Name { get; }
+
+ public string DirectoryPath
+ {
+ get => directoryPath;
+ set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value);
+ }
+
+ public class ProjectInfo
+ {
+ public string Guid;
+ public string PathRelativeToSolution;
+ public List<string> Configs = new List<string>();
+ }
+
+ public void AddNewProject(string name, ProjectInfo projectInfo)
+ {
+ projects[name] = projectInfo;
+ }
+
+ public bool HasProject(string name)
+ {
+ return projects.ContainsKey(name);
+ }
+
+ public ProjectInfo GetProjectInfo(string name)
+ {
+ return projects[name];
+ }
+
+ public bool RemoveProject(string name)
+ {
+ return projects.Remove(name);
+ }
+
+ public void Save()
+ {
+ if (!Directory.Exists(DirectoryPath))
+ throw new FileNotFoundException("The solution directory does not exist.");
+
+ string projectsDecl = string.Empty;
+ string slnPlatformsCfg = string.Empty;
+ string projPlatformsCfg = string.Empty;
+
+ bool isFirstProject = true;
+
+ foreach (var pair in projects)
+ {
+ string name = pair.Key;
+ ProjectInfo projectInfo = pair.Value;
+
+ if (!isFirstProject)
+ projectsDecl += "\n";
+
+ projectsDecl += string.Format(ProjectDeclaration,
+ name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid);
+
+ for (int i = 0; i < projectInfo.Configs.Count; i++)
+ {
+ string config = projectInfo.Configs[i];
+
+ if (i != 0 || !isFirstProject)
+ {
+ slnPlatformsCfg += "\n";
+ projPlatformsCfg += "\n";
+ }
+
+ slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config);
+ projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config);
+ }
+
+ isFirstProject = false;
+ }
+
+ string solutionPath = Path.Combine(DirectoryPath, Name + ".sln");
+ string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg);
+
+ File.WriteAllText(solutionPath, content);
+ }
+
+ public DotNetSolution(string name)
+ {
+ Name = name;
+ }
+
+ const string SolutionTemplate =
+@"Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+{0}
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+{1}
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+{2}
+ EndGlobalSection
+EndGlobal
+";
+
+ const string ProjectDeclaration =
+@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}""
+EndProject";
+
+ const string SolutionPlatformsConfig =
+@" {0}|Any CPU = {0}|Any CPU";
+
+ const string ProjectPlatformsConfig =
+@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU
+ {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU";
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
index 2871c041f5..08b8ba3946 100644
--- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
@@ -1,12 +1,12 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
<OutputType>Library</OutputType>
- <RootNamespace>GodotSharpTools</RootNamespace>
- <AssemblyName>GodotSharpTools</AssemblyName>
+ <RootNamespace>GodotTools.ProjectEditor</RootNamespace>
+ <AssemblyName>GodotTools.ProjectEditor</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
</PropertyGroup>
@@ -21,7 +21,6 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
@@ -31,25 +30,35 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.Build" />
- <Reference Include="Microsoft.Build.Framework" />
<Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL">
- <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
+ <!--
+ When building Godot with 'mono_glue=no' SCons will build this project alone instead of the
+ entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that.
+ We make SCons restore the NuGet packages in the project directory instead in this case.
+ -->
+ <HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
+ <HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
+ <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? -->
</Reference>
</ItemGroup>
<ItemGroup>
- <Compile Include="StringExtensions.cs" />
- <Compile Include="Build\BuildSystem.cs" />
- <Compile Include="Editor\MonoDevelopInstance.cs" />
- <Compile Include="Project\ProjectExtensions.cs" />
- <Compile Include="Project\IdentifierUtils.cs" />
- <Compile Include="Project\ProjectGenerator.cs" />
- <Compile Include="Project\ProjectUtils.cs" />
+ <Compile Include="ApiAssembliesInfo.cs" />
+ <Compile Include="ApiSolutionGenerator.cs" />
+ <Compile Include="DotNetSolution.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="Utils\OS.cs" />
- <Compile Include="Editor\GodotSharpExport.cs" />
+ <Compile Include="IdentifierUtils.cs" />
+ <Compile Include="ProjectExtensions.cs" />
+ <Compile Include="ProjectGenerator.cs" />
+ <Compile Include="ProjectUtils.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
+ <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
+ <Name>GodotTools.Core</Name>
+ </ProjectReference>
+ </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-</Project> \ No newline at end of file
+</Project>
diff --git a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
index 83e2d2cf8d..f93eb9a1fa 100644
--- a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Text;
-namespace GodotSharpTools.Project
+namespace GodotTools.ProjectEditor
{
public static class IdentifierUtils
{
@@ -12,7 +12,7 @@ namespace GodotSharpTools.Project
if (string.IsNullOrEmpty(qualifiedIdentifier))
throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier));
- string[] identifiers = qualifiedIdentifier.Split(new[] { '.' });
+ string[] identifiers = qualifiedIdentifier.Split('.');
for (int i = 0; i < identifiers.Length; i++)
{
@@ -66,8 +66,6 @@ namespace GodotSharpTools.Project
if (identifierBuilder.Length > startIndex || @char == '_')
identifierBuilder.Append(@char);
break;
- default:
- break;
}
}
@@ -97,14 +95,14 @@ namespace GodotSharpTools.Project
}
else
{
- if (_doubleUnderscoreKeywords.Contains(value))
+ if (DoubleUnderscoreKeywords.Contains(value))
return true;
}
- return _keywords.Contains(value);
+ return Keywords.Contains(value);
}
- static HashSet<string> _doubleUnderscoreKeywords = new HashSet<string>
+ private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string>
{
"__arglist",
"__makeref",
@@ -112,7 +110,7 @@ namespace GodotSharpTools.Project
"__refvalue",
};
- static HashSet<string> _keywords = new HashSet<string>
+ private static readonly HashSet<string> Keywords = new HashSet<string>
{
"as",
"do",
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
index 647d9ac81d..36961eb45e 100644
--- a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
@@ -1,8 +1,9 @@
+using GodotTools.Core;
using System;
using DotNet.Globbing;
using Microsoft.Build.Construction;
-namespace GodotSharpTools.Project
+namespace GodotTools.ProjectEditor
{
public static class ProjectExtensions
{
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index 89279c69a6..7cf58b6755 100644
--- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,17 +1,19 @@
+using GodotTools.Core;
using System;
+using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Construction;
-namespace GodotSharpTools.Project
+namespace GodotTools.ProjectEditor
{
public static class ProjectGenerator
{
- public const string CoreApiProjectName = "GodotSharp";
- public const string EditorApiProjectName = "GodotSharpEditor";
- const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}";
- const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}";
+ private const string CoreApiProjectName = "GodotSharp";
+ private const string EditorApiProjectName = "GodotSharpEditor";
+ private const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}";
+ private const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}";
- public static string GenCoreApiProject(string dir, string[] compileItems)
+ public static string GenCoreApiProject(string dir, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, CoreApiProjectName + ".csproj");
@@ -24,8 +26,8 @@ namespace GodotSharpTools.Project
mainGroup.SetProperty("BaseIntermediateOutputPath", "obj");
GenAssemblyInfoFile(root, dir, CoreApiProjectName,
- new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" },
- new string[] { "System.Runtime.CompilerServices" });
+ new[] {"[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]"},
+ new[] {"System.Runtime.CompilerServices"});
foreach (var item in compileItems)
{
@@ -37,7 +39,7 @@ namespace GodotSharpTools.Project
return CoreApiProjectGuid;
}
- public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems)
+ public static string GenEditorApiProject(string dir, string coreApiProjPath, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, EditorApiProjectName + ".csproj");
@@ -64,7 +66,7 @@ namespace GodotSharpTools.Project
return EditorApiProjectGuid;
}
- public static string GenGameProject(string dir, string name, string[] compileItems)
+ public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, name + ".csproj");
@@ -74,23 +76,27 @@ namespace GodotSharpTools.Project
mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
+ mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' ";
+ mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' ";
var toolsGroup = root.AddPropertyGroup();
toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' ";
toolsGroup.AddProperty("DebugSymbols", "true");
toolsGroup.AddProperty("DebugType", "portable");
toolsGroup.AddProperty("Optimize", "false");
- toolsGroup.AddProperty("DefineConstants", "DEBUG;TOOLS;");
+ toolsGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;");
toolsGroup.AddProperty("ErrorReport", "prompt");
toolsGroup.AddProperty("WarningLevel", "4");
toolsGroup.AddProperty("ConsolePause", "false");
var coreApiRef = root.AddItem("Reference", CoreApiProjectName);
+ coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll"));
coreApiRef.AddMetadata("Private", "False");
var editorApiRef = root.AddItem("Reference", EditorApiProjectName);
editorApiRef.Condition = " '$(Configuration)' == 'Tools' ";
+ editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll"));
editorApiRef.AddMetadata("Private", "False");
@@ -108,7 +114,6 @@ namespace GodotSharpTools.Project
public static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null)
{
-
string propertiesDir = Path.Combine(dir, "Properties");
if (!Directory.Exists(propertiesDir))
Directory.CreateDirectory(propertiesDir);
@@ -124,12 +129,9 @@ namespace GodotSharpTools.Project
string assemblyLinesText = string.Empty;
if (assemblyLines != null)
- {
- foreach (var assemblyLine in assemblyLines)
- assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
- }
+ assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
- string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
+ string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
@@ -161,7 +163,7 @@ namespace GodotSharpTools.Project
debugGroup.AddProperty("DebugSymbols", "true");
debugGroup.AddProperty("DebugType", "portable");
debugGroup.AddProperty("Optimize", "false");
- debugGroup.AddProperty("DefineConstants", "DEBUG;");
+ debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;");
debugGroup.AddProperty("ErrorReport", "prompt");
debugGroup.AddProperty("WarningLevel", "4");
debugGroup.AddProperty("ConsolePause", "false");
@@ -170,6 +172,7 @@ namespace GodotSharpTools.Project
releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ";
releaseGroup.AddProperty("DebugType", "portable");
releaseGroup.AddProperty("Optimize", "true");
+ releaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;");
releaseGroup.AddProperty("ErrorReport", "prompt");
releaseGroup.AddProperty("WarningLevel", "4");
releaseGroup.AddProperty("ConsolePause", "false");
@@ -193,8 +196,8 @@ namespace GodotSharpTools.Project
}
}
- private const string assemblyInfoTemplate =
-@"using System.Reflection;{0}
+ 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.
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
index a13f4fd6ef..22cf89695d 100644
--- a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -1,9 +1,10 @@
+using GodotTools.Core;
using System.Collections.Generic;
using System.IO;
using DotNet.Globbing;
using Microsoft.Build.Construction;
-namespace GodotSharpTools.Project
+namespace GodotTools.ProjectEditor
{
public static class ProjectUtils
{
@@ -53,7 +54,7 @@ namespace GodotSharpTools.Project
var glob = Glob.Parse(normalizedInclude, globOptions);
- // TODO Check somehow if path has no blog to avoid the following loop...
+ // TODO Check somehow if path has no blob to avoid the following loop...
foreach (var existingFile in existingFiles)
{
diff --git a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs
index 7115d8fc71..09333850fc 100644
--- a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs
@@ -4,12 +4,12 @@ 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("GodotSharpTools")]
+[assembly: AssemblyTitle("GodotTools.ProjectEditor")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("ignacio")]
+[assembly: AssemblyCopyright("Godot Engine contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/modules/mono/editor/GodotSharpTools/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
index 2c7cb0bd4b..13915000e4 100644
--- a/modules/mono/editor/GodotSharpTools/packages.config
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNet.Glob" version="2.1.1" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
new file mode 100644
index 0000000000..f849356919
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -0,0 +1,172 @@
+using GodotTools.Core;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
+using GodotTools.BuildLogger;
+using GodotTools.Internals;
+using GodotTools.Utils;
+using Directory = System.IO.Directory;
+
+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
+ {
+ string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin");
+
+ if (!Directory.Exists(monoWinBinDir))
+ throw new FileNotFoundException("Cannot find the Windows Mono install bin directory.");
+
+ return monoWinBinDir;
+ }
+ }
+
+ private static Godot.EditorSettings EditorSettings =>
+ GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+
+ private static bool UsingMonoMsBuildOnWindows
+ {
+ get
+ {
+ if (OS.IsWindows())
+ {
+ return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
+ == GodotSharpBuilds.BuildTool.MsBuildMono;
+ }
+
+ return false;
+ }
+ }
+
+ private static bool PrintBuildOutput =>
+ (bool) EditorSettings.GetSetting("mono/builds/print_build_output");
+
+ private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ {
+ var customPropertiesList = new List<string>();
+
+ if (customProperties != null)
+ customPropertiesList.AddRange(customProperties);
+
+ string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList);
+
+ var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs);
+
+ bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
+
+ if (!redirectOutput || Godot.OS.IsStdoutVerbose())
+ Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
+
+ startInfo.RedirectStandardOutput = redirectOutput;
+ startInfo.RedirectStandardError = redirectOutput;
+ startInfo.UseShellExecute = false;
+
+ if (UsingMonoMsBuildOnWindows)
+ {
+ // These environment variables are required for Mono's MSBuild to find the compilers.
+ // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
+ string monoWinBinDir = MonoWindowsBinDir;
+ startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
+ startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
+ startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
+ }
+
+ // Needed when running from Developer Command Prompt for VS
+ RemovePlatformVariable(startInfo.EnvironmentVariables);
+
+ var process = new Process {StartInfo = startInfo};
+
+ process.Start();
+
+ if (redirectOutput)
+ {
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+ }
+
+ return process;
+ }
+
+ public static int Build(MonoBuildInfo monoBuildInfo)
+ {
+ return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration,
+ monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
+ }
+
+ public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo)
+ {
+ return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration,
+ monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
+ }
+
+ public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ {
+ using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ {
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+ }
+
+ public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ {
+ using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ {
+ await process.WaitForExitAsync();
+
+ return process.ExitCode;
+ }
+ }
+
+ private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties)
+ {
+ string arguments = $@"""{solution}"" /v:normal /t:Rebuild ""/p:{"Configuration=" + config}"" " +
+ $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
+
+ foreach (string customProperty in customProperties)
+ {
+ arguments += " /p:" + customProperty;
+ }
+
+ return arguments;
+ }
+
+ private static void RemovePlatformVariable(StringDictionary environmentVariables)
+ {
+ // EnvironmentVariables is case sensitive? Seriously?
+
+ var platformEnvironmentVariables = new List<string>();
+
+ foreach (string env in environmentVariables.Keys)
+ {
+ if (env.ToUpper() == "PLATFORM")
+ platformEnvironmentVariables.Add(env);
+ }
+
+ foreach (string env in platformEnvironmentVariables)
+ environmentVariables.Remove(env);
+ }
+
+ private static bool IsDebugMsBuildRequested()
+ {
+ return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
new file mode 100644
index 0000000000..a0d14c43c9
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Godot;
+using GodotTools.Internals;
+using Directory = System.IO.Directory;
+using Environment = System.Environment;
+using File = System.IO.File;
+using Path = System.IO.Path;
+using OS = GodotTools.Utils.OS;
+
+namespace GodotTools.Build
+{
+ public static class MsBuildFinder
+ {
+ private static string _msbuildToolsPath = string.Empty;
+ private static string _msbuildUnixPath = string.Empty;
+ private static string _xbuildUnixPath = string.Empty;
+
+ public static string FindMsBuild()
+ {
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
+
+ if (OS.IsWindows())
+ {
+ switch (buildTool)
+ {
+ case GodotSharpBuilds.BuildTool.MsBuildVs:
+ {
+ if (_msbuildToolsPath.Empty() || !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 '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
+ }
+ }
+
+ if (!_msbuildToolsPath.EndsWith("\\"))
+ _msbuildToolsPath += "\\";
+
+ return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
+ }
+
+ case GodotSharpBuilds.BuildTool.MsBuildMono:
+ {
+ string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
+
+ if (!File.Exists(msbuildPath))
+ {
+ throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
+ }
+
+ return msbuildPath;
+ }
+
+ case GodotSharpBuilds.BuildTool.XBuild:
+ {
+ string xbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "xbuild.bat");
+
+ if (!File.Exists(xbuildPath))
+ {
+ throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameXbuild}'. Tried with path: {xbuildPath}");
+ }
+
+ return xbuildPath;
+ }
+
+ default:
+ throw new IndexOutOfRangeException("Invalid build tool in editor settings");
+ }
+ }
+
+ if (OS.IsUnix())
+ {
+ if (buildTool == GodotSharpBuilds.BuildTool.XBuild)
+ {
+ if (_xbuildUnixPath.Empty() || !File.Exists(_xbuildUnixPath))
+ {
+ // Try to search it again if it wasn't found last time or if it was removed from its location
+ _xbuildUnixPath = FindBuildEngineOnUnix("msbuild");
+ }
+
+ if (_xbuildUnixPath.Empty())
+ {
+ throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameXbuild}'");
+ }
+ }
+ else
+ {
+ if (_msbuildUnixPath.Empty() || !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");
+ }
+
+ if (_msbuildUnixPath.Empty())
+ {
+ throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'");
+ }
+ }
+
+ return buildTool != GodotSharpBuilds.BuildTool.XBuild ? _msbuildUnixPath : _xbuildUnixPath;
+ }
+
+ throw new PlatformNotSupportedException();
+ }
+
+ private static IEnumerable<string> MsBuildHintDirs
+ {
+ get
+ {
+ var result = new List<string>();
+
+ if (OS.IsOSX())
+ {
+ result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
+ result.Add("/usr/local/var/homebrew/linked/mono/bin/");
+ }
+
+ result.Add("/opt/novell/mono/bin/");
+
+ return result;
+ }
+ }
+
+ private static string FindBuildEngineOnUnix(string name)
+ {
+ string ret = OS.PathWhich(name);
+
+ if (!ret.Empty())
+ return ret;
+
+ string retFallback = OS.PathWhich($"{name}.exe");
+
+ if (!retFallback.Empty())
+ return retFallback;
+
+ foreach (string hintDir in MsBuildHintDirs)
+ {
+ string hintPath = Path.Combine(hintDir, name);
+
+ if (File.Exists(hintPath))
+ return hintPath;
+ }
+
+ return string.Empty;
+ }
+
+ private static string FindMsBuildToolsPathOnWindows()
+ {
+ if (!OS.IsWindows())
+ throw new PlatformNotSupportedException();
+
+ // Try to find 15.0 with vswhere
+
+ 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 outputArray = new Godot.Collections.Array<string>();
+ int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
+ blocking: true, output: (Godot.Collections.Array) outputArray);
+
+ if (exitCode == 0)
+ return string.Empty;
+
+ if (outputArray.Count == 0)
+ return string.Empty;
+
+ var lines = outputArray[1].Split('\n');
+
+ foreach (string line in lines)
+ {
+ int sepIdx = line.IndexOf(':');
+
+ if (sepIdx <= 0)
+ continue;
+
+ string key = line.Substring(0, sepIdx); // No need to trim
+
+ if (key != "installationPath")
+ continue;
+
+ string value = line.Substring(sepIdx + 1).StripEdges();
+
+ if (value.Empty())
+ throw new FormatException("installationPath value is empty");
+
+ if (!value.EndsWith("\\"))
+ value += "\\";
+
+ // Since VS2019, the directory is simply named "Current"
+ string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin");
+
+ if (Directory.Exists(msbuildDir))
+ return msbuildDir;
+
+ // Directory name "15.0" is used in VS 2017
+ return Path.Combine(value, "MSBuild\\15.0\\Bin");
+ }
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs
new file mode 100644
index 0000000000..3ba311c283
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs
@@ -0,0 +1,115 @@
+using Godot;
+using System;
+using System.Collections.Generic;
+using Godot.Collections;
+using GodotTools.Internals;
+using GodotTools.ProjectEditor;
+using File = GodotTools.Utils.File;
+using Directory = GodotTools.Utils.Directory;
+
+namespace GodotTools
+{
+ public static class CSharpProject
+ {
+ public static string GenerateGameProject(string dir, string name, IEnumerable<string> files = null)
+ {
+ try
+ {
+ return ProjectGenerator.GenGameProject(dir, name, files);
+ }
+ catch (Exception e)
+ {
+ GD.PushError(e.ToString());
+ return string.Empty;
+ }
+ }
+
+ public static void AddItem(string projectPath, string itemType, string include)
+ {
+ if (!(bool) Internal.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)
+ {
+ TimeSpan elapsedTime = value - Epoch;
+ return (ulong) elapsedTime.TotalSeconds;
+ }
+
+ public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+ {
+ if (File.Exists(outputPath))
+ File.Delete(outputPath);
+
+ var oldDict = Internal.GetScriptsMetadataOrNothing();
+ var newDict = new Godot.Collections.Dictionary<string, object>();
+
+ foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
+ {
+ string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
+
+ ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
+
+ if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
+ {
+ var oldFileDict = (Dictionary) oldFileVar;
+
+ if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime))
+ {
+ if (storedModifiedTime == modifiedTime)
+ {
+ // No changes so no need to parse again
+ newDict[projectIncludeFile] = oldFileDict;
+ continue;
+ }
+ }
+ }
+
+ ScriptClassParser.ParseFileOrThrow(projectIncludeFile, out var classes);
+
+ string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
+
+ var classDict = new Dictionary();
+
+ foreach (var classDecl in classes)
+ {
+ if (classDecl.BaseCount == 0)
+ continue; // Does not inherit nor implement anything, so it can't be a script class
+
+ string classCmp = classDecl.Nested ?
+ classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
+ classDecl.Name;
+
+ if (classCmp != searchName)
+ continue;
+
+ classDict["namespace"] = classDecl.Namespace;
+ classDict["class_name"] = classDecl.Name;
+ classDict["nested"] = classDecl.Nested;
+ break;
+ }
+
+ if (classDict.Count == 0)
+ continue; // Not found
+
+ newDict[projectIncludeFile] = new Dictionary {["modified_time"] = $"{modifiedTime}", ["class"] = classDict};
+ }
+
+ if (newDict.Count > 0)
+ {
+ string json = JSON.Print(newDict);
+
+ string baseDir = outputPath.GetBaseDir();
+
+ if (!Directory.Exists(baseDir))
+ Directory.CreateDirectory(baseDir);
+
+ File.WriteAllText(outputPath, json);
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs
new file mode 100644
index 0000000000..433a931941
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs
@@ -0,0 +1,396 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using GodotTools.Build;
+using GodotTools.Internals;
+using GodotTools.Utils;
+using Error = Godot.Error;
+using File = GodotTools.Utils.File;
+using Directory = GodotTools.Utils.Directory;
+
+namespace GodotTools
+{
+ public static class GodotSharpBuilds
+ {
+ private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>();
+
+ public const string PropNameMsbuildMono = "MSBuild (Mono)";
+ public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
+ public const string PropNameXbuild = "xbuild (Deprecated)";
+
+ public const string MsBuildIssuesFileName = "msbuild_issues.csv";
+ public const string MsBuildLogFileName = "msbuild_log.txt";
+
+ public enum BuildTool
+ {
+ MsBuildMono,
+ MsBuildVs,
+ XBuild // Deprecated
+ }
+
+ private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo)
+ {
+ var issuesFile = GetIssuesFilePath(buildInfo);
+
+ if (!File.Exists(issuesFile))
+ return;
+
+ File.Delete(issuesFile);
+ }
+
+ private static string _ApiFolderName(ApiAssemblyType apiType)
+ {
+ ulong apiHash = apiType == ApiAssemblyType.Core ?
+ Internal.GetCoreApiHash() :
+ Internal.GetEditorApiHash();
+ return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}";
+ }
+
+ private static void ShowBuildErrorDialog(string message)
+ {
+ GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
+ GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab();
+ }
+
+ public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
+ public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
+
+ private static string GetLogFilePath(MonoBuildInfo buildInfo)
+ {
+ return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
+ }
+
+ private static string GetIssuesFilePath(MonoBuildInfo buildInfo)
+ {
+ return Path.Combine(Godot.ProjectSettings.LocalizePath(buildInfo.LogsDirPath), MsBuildIssuesFileName);
+ }
+
+ private static void PrintVerbose(string text)
+ {
+ if (Godot.OS.IsStdoutVerbose())
+ Godot.GD.Print(text);
+ }
+
+ public static bool Build(MonoBuildInfo buildInfo)
+ {
+ if (BuildsInProgress.Contains(buildInfo))
+ throw new InvalidOperationException("A build is already in progress");
+
+ BuildsInProgress.Add(buildInfo);
+
+ try
+ {
+ MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
+ buildTab.OnBuildStart();
+
+ // Required in order to update the build tasks list
+ Internal.GodotMainIteration();
+
+ try
+ {
+ RemoveOldIssuesFile(buildInfo);
+ }
+ catch (IOException e)
+ {
+ buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
+ Console.Error.WriteLine(e);
+ }
+
+ try
+ {
+ int exitCode = BuildSystem.Build(buildInfo);
+
+ if (exitCode != 0)
+ PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
+
+ buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+ finally
+ {
+ BuildsInProgress.Remove(buildInfo);
+ }
+ }
+
+ public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo)
+ {
+ if (BuildsInProgress.Contains(buildInfo))
+ throw new InvalidOperationException("A build is already in progress");
+
+ BuildsInProgress.Add(buildInfo);
+
+ try
+ {
+ MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
+
+ try
+ {
+ RemoveOldIssuesFile(buildInfo);
+ }
+ catch (IOException e)
+ {
+ buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
+ Console.Error.WriteLine(e);
+ }
+
+ try
+ {
+ int exitCode = await BuildSystem.BuildAsync(buildInfo);
+
+ if (exitCode != 0)
+ PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
+
+ buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+ finally
+ {
+ BuildsInProgress.Remove(buildInfo);
+ }
+ }
+
+ public static bool BuildApiSolution(string apiSlnDir, string config)
+ {
+ string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln");
+
+ string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config);
+ string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll");
+
+ string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config);
+ string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll");
+
+ if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile))
+ return true; // The assemblies are in the output folder; assume the solution is already built
+
+ var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config);
+
+ // TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
+ // once we start to actively document manually maintained C# classes
+ apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings
+
+ if (Build(apiBuildInfo))
+ return true;
+
+ ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution.");
+ return false;
+ }
+
+ private static bool CopyApiAssembly(string srcDir, string dstDir, string assemblyName, ApiAssemblyType apiType)
+ {
+ // Create destination directory if needed
+ if (!Directory.Exists(dstDir))
+ {
+ try
+ {
+ Directory.CreateDirectory(dstDir);
+ }
+ catch (IOException e)
+ {
+ ShowBuildErrorDialog($"Failed to create destination directory for the API assemblies. Exception message: {e.Message}");
+ return false;
+ }
+ }
+
+ string assemblyFile = assemblyName + ".dll";
+ string assemblySrc = Path.Combine(srcDir, assemblyFile);
+ string assemblyDst = Path.Combine(dstDir, assemblyFile);
+
+ if (!File.Exists(assemblyDst) || File.GetLastWriteTime(assemblySrc) > File.GetLastWriteTime(assemblyDst) ||
+ Internal.MetadataIsApiAssemblyInvalidated(apiType))
+ {
+ string xmlFile = $"{assemblyName}.xml";
+ string pdbFile = $"{assemblyName}.pdb";
+
+ try
+ {
+ File.Copy(Path.Combine(srcDir, xmlFile), Path.Combine(dstDir, xmlFile));
+ }
+ catch (IOException e)
+ {
+ Godot.GD.PushWarning(e.ToString());
+ }
+
+ try
+ {
+ File.Copy(Path.Combine(srcDir, pdbFile), Path.Combine(dstDir, pdbFile));
+ }
+ catch (IOException e)
+ {
+ Godot.GD.PushWarning(e.ToString());
+ }
+
+ try
+ {
+ File.Copy(assemblySrc, assemblyDst);
+ }
+ catch (IOException e)
+ {
+ ShowBuildErrorDialog($"Failed to copy {assemblyFile}. Exception message: {e.Message}");
+ return false;
+ }
+
+ Internal.MetadataSetApiAssemblyInvalidated(apiType, false);
+ }
+
+ return true;
+ }
+
+ public static bool MakeApiAssembly(ApiAssemblyType apiType, string config)
+ {
+ string apiName = apiType == ApiAssemblyType.Core ? ApiAssemblyNames.Core : ApiAssemblyNames.Editor;
+
+ string editorPrebuiltApiDir = Path.Combine(GodotSharpDirs.DataEditorPrebuiltApiDir, config);
+ string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, config);
+
+ if (File.Exists(Path.Combine(editorPrebuiltApiDir, $"{apiName}.dll")))
+ {
+ using (var copyProgress = new EditorProgress("mono_copy_prebuilt_api_assembly", $"Copying prebuilt {apiName} assembly...", 1))
+ {
+ copyProgress.Step($"Copying {apiName} assembly", 0);
+ return CopyApiAssembly(editorPrebuiltApiDir, resAssembliesDir, apiName, apiType);
+ }
+ }
+
+ const string apiSolutionName = ApiAssemblyNames.SolutionName;
+
+ using (var pr = new EditorProgress($"mono_build_release_{apiSolutionName}", $"Building {apiSolutionName} solution...", 3))
+ {
+ pr.Step($"Generating {apiSolutionName} solution", 0);
+
+ string apiSlnDir = Path.Combine(GodotSharpDirs.MonoSolutionsDir, _ApiFolderName(ApiAssemblyType.Core));
+ string apiSlnFile = Path.Combine(apiSlnDir, $"{apiSolutionName}.sln");
+
+ if (!Directory.Exists(apiSlnDir) || !File.Exists(apiSlnFile))
+ {
+ var bindingsGenerator = new BindingsGenerator();
+
+ if (!Godot.OS.IsStdoutVerbose())
+ bindingsGenerator.LogPrintEnabled = false;
+
+ Error err = bindingsGenerator.GenerateCsApi(apiSlnDir);
+ if (err != Error.Ok)
+ {
+ ShowBuildErrorDialog($"Failed to generate {apiSolutionName} solution. Error: {err}");
+ return false;
+ }
+ }
+
+ pr.Step($"Building {apiSolutionName} solution", 1);
+
+ if (!BuildApiSolution(apiSlnDir, config))
+ return false;
+
+ pr.Step($"Copying {apiName} assembly", 2);
+
+ // Copy the built assembly to the assemblies directory
+ string apiAssemblyDir = Path.Combine(apiSlnDir, apiName, "bin", config);
+ if (!CopyApiAssembly(apiAssemblyDir, resAssembliesDir, apiName, apiType))
+ return false;
+ }
+
+ return true;
+ }
+
+ public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return true; // No solution to build
+
+ string apiConfig = config == "Release" ? "Release" : "Debug";
+
+ if (!MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
+ return false;
+
+ if (!MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
+ return false;
+
+ using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
+ {
+ pr.Step("Building project solution", 0);
+
+ var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config);
+
+ // Add Godot defines
+ string constants = OS.IsWindows() ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
+
+ foreach (var godotDefine in godotDefines)
+ constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
+
+ if (Internal.GodotIsRealTDouble())
+ constants += "GODOT_REAL_T_IS_DOUBLE;";
+
+ constants += OS.IsWindows() ? "\"" : "\\\"";
+
+ buildInfo.CustomProperties.Add(constants);
+
+ if (!Build(buildInfo))
+ {
+ ShowBuildErrorDialog("Failed to build project solution");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool EditorBuildCallback()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return true; // No solution to build
+
+ string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
+ string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
+
+ CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
+
+ if (File.Exists(editorScriptsMetadataPath))
+ File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
+
+ var godotDefines = new[]
+ {
+ Godot.OS.GetName(),
+ Internal.GodotIs32Bits() ? "32" : "64"
+ };
+
+ return BuildProjectBlocking("Tools", godotDefines);
+ }
+
+ public static void Initialize()
+ {
+ // Build tool settings
+
+ Internal.EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono);
+
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+
+ editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = Godot.Variant.Type.Int,
+ ["name"] = "mono/builds/build_tool",
+ ["hint"] = Godot.PropertyHint.Enum,
+ ["hint_string"] = OS.IsWindows() ?
+ $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameXbuild}" :
+ $"{PropNameMsbuildMono},{PropNameXbuild}"
+ });
+
+ Internal.EditorDef("mono/builds/print_build_output", false);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
new file mode 100644
index 0000000000..955574d5fe
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -0,0 +1,538 @@
+using Godot;
+using GodotTools.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using GodotTools.Internals;
+using GodotTools.ProjectEditor;
+using File = GodotTools.Utils.File;
+using Path = System.IO.Path;
+using OS = GodotTools.Utils.OS;
+
+namespace GodotTools
+{
+ public class GodotSharpEditor : EditorPlugin, ISerializationListener
+ {
+ private EditorSettings editorSettings;
+
+ private PopupMenu menuPopup;
+
+ private AcceptDialog errorDialog;
+ private AcceptDialog aboutDialog;
+ private CheckBox aboutDialogCheckBox;
+
+ private ToolButton bottomPanelBtn;
+
+ private MonoDevelopInstance monoDevelopInstance;
+ private MonoDevelopInstance visualStudioForMacInstance;
+
+ public MonoBottomPanel MonoBottomPanel { get; private set; }
+
+ private bool CreateProjectSolution()
+ {
+ using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...", 2)) // TTR("Generating solution...")
+ {
+ pr.Step("Generating C# project..."); // TTR("Generating C# project...")
+
+ string resourceDir = ProjectSettings.GlobalizePath("res://");
+
+ string path = resourceDir;
+ string name = (string) ProjectSettings.GetSetting("application/config/name");
+ if (name.Empty())
+ name = "UnnamedProject";
+
+ string guid = CSharpProject.GenerateGameProject(path, name);
+
+ if (guid.Length > 0)
+ {
+ var solution = new DotNetSolution(name)
+ {
+ DirectoryPath = path
+ };
+
+ var projectInfo = new DotNetSolution.ProjectInfo
+ {
+ Guid = guid,
+ PathRelativeToSolution = name + ".csproj",
+ Configs = new List<string> {"Debug", "Release", "Tools"}
+ };
+
+ solution.AddNewProject(name, projectInfo);
+
+ try
+ {
+ solution.Save();
+ }
+ catch (IOException e)
+ {
+ ShowErrorDialog($"Failed to save solution. Exception message: {e.Message}"); // TTR
+ return false;
+ }
+
+ string apiConfig = "Debug";
+
+ if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
+ return false;
+
+ if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
+ return false;
+
+ pr.Step("Done"); // TTR("Done")
+
+ // Here, after all calls to progress_task_step
+ CallDeferred(nameof(_RemoveCreateSlnMenuOption));
+ }
+ else
+ {
+ ShowErrorDialog("Failed to create C# project."); // TTR
+ }
+
+ return true;
+ }
+ }
+
+ private static int _makeApiSolutionsAttempts = 100;
+ private static bool _makeApiSolutionsRecursionGuard = false;
+
+ private void _MakeApiSolutionsIfNeeded()
+ {
+ // I'm sick entirely of ProgressDialog
+
+ if (Internal.IsMessageQueueFlushing() || Engine.GetMainLoop() == null)
+ {
+ if (_makeApiSolutionsAttempts == 0) // This better never happen or I swear...
+ throw new TimeoutException();
+
+ if (Engine.GetMainLoop() != null)
+ {
+ if (!Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
+ Engine.GetMainLoop().Connect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
+ }
+ else
+ {
+ CallDeferred(nameof(_MakeApiSolutionsIfNeededImpl));
+ }
+
+ _makeApiSolutionsAttempts--;
+ return;
+ }
+
+ // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike
+ // the message queue, with signals the collateral damage should be minimal in the worst case.
+ if (!_makeApiSolutionsRecursionGuard)
+ {
+ _makeApiSolutionsRecursionGuard = true;
+
+ // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead
+ if (Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
+ Engine.GetMainLoop().Disconnect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
+
+ _MakeApiSolutionsIfNeededImpl();
+
+ _makeApiSolutionsRecursionGuard = false;
+ }
+ }
+
+ private void _MakeApiSolutionsIfNeededImpl()
+ {
+ // If the project has a solution and C# project make sure the API assemblies are present and up to date
+
+ string api_config = "Debug";
+ string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, api_config);
+
+ if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Core}.dll")) ||
+ Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Core))
+ {
+ if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, api_config))
+ return;
+ }
+
+ if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Editor}.dll")) ||
+ Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Editor))
+ {
+ if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, api_config))
+ return; // Redundant? I don't think so!
+ }
+ }
+
+ private void _RemoveCreateSlnMenuOption()
+ {
+ menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln));
+ bottomPanelBtn.Show();
+ }
+
+ private void _ShowAboutDialog()
+ {
+ bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
+ aboutDialogCheckBox.Pressed = showOnStart;
+ aboutDialog.PopupCenteredMinsize();
+ }
+
+ private void _ToggleAboutDialogOnStart(bool enabled)
+ {
+ bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
+ if (showOnStart != enabled)
+ editorSettings.SetSetting("mono/editor/show_info_on_start", enabled);
+ }
+
+ private void _MenuOptionPressed(MenuOptions id)
+ {
+ switch (id)
+ {
+ case MenuOptions.CreateSln:
+ CreateProjectSolution();
+ break;
+ case MenuOptions.AboutCSharp:
+ _ShowAboutDialog();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option");
+ }
+ }
+
+ private void _BuildSolutionPressed()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ {
+ if (!CreateProjectSolution())
+ return; // Failed to create solution
+ }
+
+ Instance.MonoBottomPanel.BuildProjectPressed();
+ }
+
+ public override void _Notification(int what)
+ {
+ base._Notification(what);
+
+ if (what == NotificationReady)
+ {
+ bool showInfoDialog = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
+ if (showInfoDialog)
+ {
+ aboutDialog.PopupExclusive = true;
+ _ShowAboutDialog();
+ // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
+ aboutDialog.PopupExclusive = false;
+ }
+ }
+ }
+
+ public enum MenuOptions
+ {
+ CreateSln,
+ AboutCSharp,
+ }
+
+ public enum ExternalEditor
+ {
+ None,
+ VisualStudio, // TODO (Windows-only)
+ VisualStudioForMac, // Mac-only
+ MonoDevelop,
+ VsCode
+ }
+
+ public void ShowErrorDialog(string message, string title = "Error")
+ {
+ errorDialog.WindowTitle = title;
+ errorDialog.DialogText = message;
+ errorDialog.PopupCenteredMinsize();
+ }
+
+ private static string _vsCodePath = string.Empty;
+
+ private static readonly string[] VsCodeNames =
+ {
+ "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss"
+ };
+
+ public Error OpenInExternalEditor(Script script, int line, int col)
+ {
+ var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor");
+
+ switch (editor)
+ {
+ case ExternalEditor.VsCode:
+ {
+ if (_vsCodePath.Empty() || !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);
+ }
+
+ var args = new List<string>();
+
+ bool osxAppBundleInstalled = false;
+
+ if (OS.IsOSX())
+ {
+ // The package path is '/Applications/Visual Studio Code.app'
+ const string vscodeBundleId = "com.microsoft.VSCode";
+
+ osxAppBundleInstalled = Internal.IsOsxAppBundleInstalled(vscodeBundleId);
+
+ if (osxAppBundleInstalled)
+ {
+ args.Add("-b");
+ args.Add(vscodeBundleId);
+
+ // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
+ // editing our folder. It's better to ask for a new window and let VSCode do the window management.
+ args.Add("-n");
+
+ // The open process must wait until the application finishes (which is instant in VSCode's case)
+ args.Add("--wait-apps");
+
+ args.Add("--args");
+ }
+ }
+
+ var resourcePath = ProjectSettings.GlobalizePath("res://");
+ args.Add(resourcePath);
+
+ string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
+
+ if (line >= 0)
+ {
+ args.Add("-g");
+ args.Add($"{scriptPath}:{line + 1}:{col}");
+ }
+ else
+ {
+ args.Add(scriptPath);
+ }
+
+ string command;
+
+ if (OS.IsOSX())
+ {
+ if (!osxAppBundleInstalled && _vsCodePath.Empty())
+ {
+ GD.PushError("Cannot find code editor: VSCode");
+ return Error.FileNotFound;
+ }
+
+ command = osxAppBundleInstalled ? "/usr/bin/open" : _vsCodePath;
+ }
+ else
+ {
+ if (_vsCodePath.Empty())
+ {
+ GD.PushError("Cannot find code editor: VSCode");
+ return Error.FileNotFound;
+ }
+
+ command = _vsCodePath;
+ }
+
+ try
+ {
+ OS.RunProcess(command, args);
+ }
+ catch (Exception e)
+ {
+ GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'");
+ }
+
+ break;
+ }
+
+ case ExternalEditor.VisualStudioForMac:
+ goto case ExternalEditor.MonoDevelop;
+ case ExternalEditor.MonoDevelop:
+ {
+ MonoDevelopInstance GetMonoDevelopInstance(string solutionPath)
+ {
+ if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac)
+ {
+ if (visualStudioForMacInstance == null)
+ visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac);
+
+ return visualStudioForMacInstance;
+ }
+
+ if (monoDevelopInstance == null)
+ monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop);
+
+ return monoDevelopInstance;
+ }
+
+ string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
+
+ if (line >= 0)
+ scriptPath += $";{line + 1};{col}";
+
+ GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath);
+
+ break;
+ }
+
+ case ExternalEditor.None:
+ return Error.Unavailable;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return Error.Ok;
+ }
+
+ public bool OverridesExternalEditor()
+ {
+ return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None;
+ }
+
+ public override bool Build()
+ {
+ return GodotSharpBuilds.EditorBuildCallback();
+ }
+
+ public override void EnablePlugin()
+ {
+ base.EnablePlugin();
+
+ if (Instance != null)
+ throw new InvalidOperationException();
+ Instance = this;
+
+ var editorInterface = GetEditorInterface();
+ var editorBaseControl = editorInterface.GetBaseControl();
+
+ editorSettings = editorInterface.GetEditorSettings();
+
+ errorDialog = new AcceptDialog();
+ editorBaseControl.AddChild(errorDialog);
+
+ MonoBottomPanel = new MonoBottomPanel();
+
+ bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono"); // TTR("Mono")
+
+ AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
+
+ menuPopup = new PopupMenu();
+ menuPopup.Hide();
+ menuPopup.SetAsToplevel(true);
+
+ AddToolSubmenuItem("Mono", menuPopup);
+
+ // TODO: Remove or edit this info dialog once Mono support is no longer in alpha
+ {
+ menuPopup.AddItem("About C# support", (int) MenuOptions.AboutCSharp); // TTR("About C# support")
+ aboutDialog = new AcceptDialog();
+ editorBaseControl.AddChild(aboutDialog);
+ aboutDialog.WindowTitle = "Important: C# support is not feature-complete";
+
+ // We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
+ // we'll add. Instead we add containers and a new autowrapped Label inside.
+
+ // Main VBoxContainer (icon + label on top, checkbox at bottom)
+ var aboutVBox = new VBoxContainer();
+ aboutDialog.AddChild(aboutVBox);
+
+ // HBoxContainer for icon + label
+ var aboutHBox = new HBoxContainer();
+ aboutVBox.AddChild(aboutHBox);
+
+ var aboutIcon = new TextureRect();
+ aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons");
+ aboutHBox.AddChild(aboutIcon);
+
+ var aboutLabel = new Label();
+ aboutHBox.AddChild(aboutLabel);
+ aboutLabel.RectMinSize = new Vector2(600, 150) * Internal.EditorScale;
+ aboutLabel.SizeFlagsVertical = (int) Control.SizeFlags.ExpandFill;
+ aboutLabel.Autowrap = true;
+ 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 and Windows, but not yet to mobile or web platforms. " +
+ "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" +
+ " https://github.com/godotengine/godot/issues\n\n" +
+ "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!";
+
+ Internal.EditorDef("mono/editor/show_info_on_start", true);
+
+ // CheckBox in main container
+ aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"};
+ aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart));
+ aboutVBox.AddChild(aboutDialogCheckBox);
+ }
+
+ if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
+ {
+ // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized.
+ CallDeferred(nameof(_MakeApiSolutionsIfNeeded));
+ }
+ else
+ {
+ bottomPanelBtn.Hide();
+ menuPopup.AddItem("Create C# solution", (int) MenuOptions.CreateSln); // TTR("Create C# solution")
+ }
+
+ menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed));
+
+ var buildButton = new ToolButton
+ {
+ Text = "Build",
+ HintTooltip = "Build solution",
+ FocusMode = Control.FocusModeEnum.None
+ };
+ buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed));
+ AddControlToContainer(CustomControlContainer.Toolbar, buildButton);
+
+ // External editor settings
+ Internal.EditorDef("mono/editor/external_editor", ExternalEditor.None);
+
+ string settingsHintStr = "Disabled";
+
+ if (OS.IsWindows())
+ {
+ settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditor.VsCode}";
+ }
+ else if (OS.IsOSX())
+ {
+ settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" +
+ $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditor.VsCode}";
+ }
+ else if (OS.IsUnix())
+ {
+ settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditor.VsCode}";
+ }
+
+ editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = Variant.Type.Int,
+ ["name"] = "mono/editor/external_editor",
+ ["hint"] = PropertyHint.Enum,
+ ["hint_string"] = settingsHintStr
+ });
+
+ // Export plugin
+ AddExportPlugin(new GodotSharpExport());
+
+ GodotSharpBuilds.Initialize();
+ }
+
+ public void OnBeforeSerialize()
+ {
+ }
+
+ public void OnAfterDeserialize()
+ {
+ Instance = this;
+ }
+
+ // Singleton
+
+ public static GodotSharpEditor Instance { get; private set; }
+
+ private GodotSharpEditor()
+ {
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs
new file mode 100644
index 0000000000..b80fe1fab7
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs
@@ -0,0 +1,197 @@
+using Godot;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using GodotTools.Core;
+using GodotTools.Internals;
+using Directory = GodotTools.Utils.Directory;
+using File = GodotTools.Utils.File;
+using Path = System.IO.Path;
+
+namespace GodotTools
+{
+ public class GodotSharpExport : EditorExportPlugin
+ {
+ private void AddFile(string srcPath, string dstPath, bool remap = false)
+ {
+ AddFile(dstPath, File.ReadAllBytes(srcPath), remap);
+ }
+
+ public override void _ExportFile(string path, string type, string[] features)
+ {
+ base._ExportFile(path, type, features);
+
+ if (type != Internal.CSharpLanguageType)
+ return;
+
+ if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}")
+ throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path));
+
+ // TODO What if the source file is not part of the game's C# project
+
+ bool includeScriptsContent = (bool) ProjectSettings.GetSetting("mono/export/include_scripts_content");
+
+ if (!includeScriptsContent)
+ {
+ // We don't want to include the source code on exported games
+ AddFile(path, new byte[] { }, remap: false);
+ Skip();
+ }
+ }
+
+ public override void _ExportBegin(string[] features, bool isDebug, string path, int flags)
+ {
+ base._ExportBegin(features, isDebug, path, flags);
+
+ try
+ {
+ _ExportBeginImpl(features, isDebug, path, flags);
+ }
+ catch (Exception e)
+ {
+ GD.PushError($"Failed to export project. Exception message: {e.Message}");
+ Console.Error.WriteLine(e);
+ }
+ }
+
+ public void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
+ {
+ // TODO Right now there is no way to stop the export process with an error
+
+ if (File.Exists(GodotSharpDirs.ProjectSlnPath))
+ {
+ string buildConfig = isDebug ? "Debug" : "Release";
+
+ string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
+ CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
+
+ AddFile(scriptsMetadataPath, scriptsMetadataPath);
+
+ // Turn export features into defines
+ var godotDefines = features;
+
+ if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines))
+ {
+ GD.PushError("Failed to build project");
+ return;
+ }
+
+ // Add dependency assemblies
+
+ var dependencies = new Godot.Collections.Dictionary<string, string>();
+
+ var projectDllName = (string) ProjectSettings.GetSetting("application/config/name");
+ if (projectDllName.Empty())
+ {
+ projectDllName = "UnnamedProject";
+ }
+
+ string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
+ string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
+
+ dependencies[projectDllName] = projectDllSrcPath;
+
+ {
+ string templatesDir = Internal.FullTemplatesDir;
+ string androidBclDir = Path.Combine(templatesDir, "android-bcl");
+
+ string customLibDir = features.Contains("Android") && Directory.Exists(androidBclDir) ? androidBclDir : string.Empty;
+
+ GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies);
+ }
+
+ string apiConfig = isDebug ? "Debug" : "Release";
+ string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig);
+
+ foreach (var dependency in dependencies)
+ {
+ string dependSrcPath = dependency.Value;
+ string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile());
+ AddFile(dependSrcPath, dependDstPath);
+ }
+ }
+
+ // Mono specific export template extras (data dir)
+ ExportDataDirectory(features, isDebug, path);
+ }
+
+ private static void ExportDataDirectory(IEnumerable<string> features, bool debug, string path)
+ {
+ var featureSet = new HashSet<string>(features);
+
+ if (!PlatformHasTemplateDir(featureSet))
+ return;
+
+ string templateDirName = "data.mono";
+
+ if (featureSet.Contains("Windows"))
+ {
+ templateDirName += ".windows";
+ templateDirName += featureSet.Contains("64") ? ".64" : ".32";
+ }
+ else if (featureSet.Contains("X11"))
+ {
+ templateDirName += ".x11";
+ templateDirName += featureSet.Contains("64") ? ".64" : ".32";
+ }
+ else
+ {
+ throw new NotSupportedException("Target platform not supported");
+ }
+
+ templateDirName += debug ? ".release_debug" : ".release";
+
+ string templateDirPath = Path.Combine(Internal.FullTemplatesDir, templateDirName);
+
+ if (!Directory.Exists(templateDirPath))
+ throw new FileNotFoundException("Data template directory not found");
+
+ string outputDir = new FileInfo(path).Directory?.FullName ??
+ throw new FileNotFoundException("Base directory not found");
+
+ string outputDataDir = Path.Combine(outputDir, DataDirName);
+
+ if (Directory.Exists(outputDataDir))
+ Directory.Delete(outputDataDir, recursive: true); // Clean first
+
+ Directory.CreateDirectory(outputDataDir);
+
+ foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories))
+ {
+ Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1)));
+ }
+
+ foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories))
+ {
+ File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
+ }
+ }
+
+ private static bool PlatformHasTemplateDir(IEnumerable<string> featureSet)
+ {
+ // OSX export templates are contained in a zip, so we place
+ // our custom template inside it and let Godot do the rest.
+ return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f));
+ }
+
+ private static string DataDirName
+ {
+ get
+ {
+ var appName = (string) ProjectSettings.GetSetting("application/config/name");
+ string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false);
+ return $"data_{appNameSafe}";
+ }
+ }
+
+ private static void GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath,
+ string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies) =>
+ internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath,
+ string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
new file mode 100644
index 0000000000..a0ff8a0df1
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <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.5</TargetFrameworkVersion>
+ <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
+ <GodotApiConfiguration>Debug</GodotApiConfiguration>
+ </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="GodotSharp">
+ <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath>
+ </Reference>
+ <Reference Include="GodotSharpEditor">
+ <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Build\MsBuildFinder.cs" />
+ <Compile Include="Internals\BindingsGenerator.cs" />
+ <Compile Include="Internals\EditorProgress.cs" />
+ <Compile Include="Internals\GodotSharpDirs.cs" />
+ <Compile Include="Internals\Internal.cs" />
+ <Compile Include="Internals\ScriptClassParser.cs" />
+ <Compile Include="MonoDevelopInstance.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Build\BuildSystem.cs" />
+ <Compile Include="Utils\Directory.cs" />
+ <Compile Include="Utils\File.cs" />
+ <Compile Include="Utils\OS.cs" />
+ <Compile Include="GodotSharpEditor.cs" />
+ <Compile Include="GodotSharpBuilds.cs" />
+ <Compile Include="HotReloadAssemblyWatcher.cs" />
+ <Compile Include="MonoBuildInfo.cs" />
+ <Compile Include="MonoBuildTab.cs" />
+ <Compile Include="MonoBottomPanel.cs" />
+ <Compile Include="GodotSharpExport.cs" />
+ <Compile Include="CSharpProject.cs" />
+ <Compile Include="Utils\CollectionExtensions.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.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>
+ <Folder Include="Editor" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
new file mode 100644
index 0000000000..aa52079cf4
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
@@ -0,0 +1,47 @@
+using Godot;
+using GodotTools.Internals;
+
+namespace GodotTools
+{
+ public class HotReloadAssemblyWatcher : Node
+ {
+ private Timer watchTimer;
+
+ public override void _Notification(int what)
+ {
+ if (what == MainLoop.NotificationWmFocusIn)
+ {
+ RestartTimer();
+
+ if (Internal.IsAssembliesReloadingNeeded())
+ Internal.ReloadAssemblies(softReload: false);
+ }
+ }
+
+ private void TimerTimeout()
+ {
+ if (Internal.IsAssembliesReloadingNeeded())
+ Internal.ReloadAssemblies(softReload: false);
+ }
+
+ public void RestartTimer()
+ {
+ watchTimer.Stop();
+ watchTimer.Start();
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ watchTimer = new Timer
+ {
+ OneShot = false,
+ WaitTime = (float) Internal.EditorDef("mono/assembly_watch_interval_sec", 0.5)
+ };
+ watchTimer.Connect("timeout", this, nameof(TimerTimeout));
+ AddChild(watchTimer);
+ watchTimer.Start();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs
new file mode 100644
index 0000000000..1daa5e138e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace GodotTools.Internals
+{
+ public class BindingsGenerator : IDisposable
+ {
+ class BindingsGeneratorSafeHandle : SafeHandle
+ {
+ public BindingsGeneratorSafeHandle(IntPtr handle) : base(IntPtr.Zero, true)
+ {
+ this.handle = handle;
+ }
+
+ public override bool IsInvalid => handle == IntPtr.Zero;
+
+ protected override bool ReleaseHandle()
+ {
+ internal_Dtor(handle);
+ return true;
+ }
+ }
+
+ private BindingsGeneratorSafeHandle safeHandle;
+ private bool disposed = false;
+
+ public bool LogPrintEnabled
+ {
+ get => internal_LogPrintEnabled(GetPtr());
+ set => internal_SetLogPrintEnabled(GetPtr(), value);
+ }
+
+ public static uint Version => internal_Version();
+ public static uint CsGlueVersion => internal_CsGlueVersion();
+
+ public Godot.Error GenerateCsApi(string outputDir) => internal_GenerateCsApi(GetPtr(), outputDir);
+
+ internal IntPtr GetPtr()
+ {
+ if (disposed)
+ throw new ObjectDisposedException(GetType().FullName);
+
+ return safeHandle.DangerousGetHandle();
+ }
+
+ public void Dispose()
+ {
+ if (disposed)
+ return;
+
+ if (safeHandle != null && !safeHandle.IsInvalid)
+ {
+ safeHandle.Dispose();
+ safeHandle = null;
+ }
+
+ disposed = true;
+ }
+
+ public BindingsGenerator()
+ {
+ safeHandle = new BindingsGeneratorSafeHandle(internal_Ctor());
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern IntPtr internal_Ctor();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_Dtor(IntPtr handle);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_LogPrintEnabled(IntPtr handle);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_SetLogPrintEnabled(IntPtr handle, bool enabled);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern Godot.Error internal_GenerateCsApi(IntPtr handle, string outputDir);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern uint internal_Version();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern uint internal_CsGlueVersion();
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs
new file mode 100644
index 0000000000..70ba7c733a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Runtime.CompilerServices;
+using Godot;
+
+namespace GodotTools.Internals
+{
+ public class EditorProgress : IDisposable
+ {
+ public string Task { get; }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_Create(string task, string label, int amount, bool canCancel);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_Dispose(string task);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_Step(string task, string state, int step, bool forceRefresh);
+
+ public EditorProgress(string task, string label, int amount, bool canCancel = false)
+ {
+ Task = task;
+ internal_Create(task, label, amount, canCancel);
+ }
+
+ ~EditorProgress()
+ {
+ // Should never rely on the GC to dispose EditorProgress.
+ // It should be disposed immediately when the task finishes.
+ GD.PushError("EditorProgress disposed by the Garbage Collector");
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ internal_Dispose(Task);
+ GC.SuppressFinalize(this);
+ }
+
+ public void Step(string state, int step = -1, bool forceRefresh = true)
+ {
+ internal_Step(Task, state, step, forceRefresh);
+ }
+
+ public bool TryStep(string state, int step = -1, bool forceRefresh = true)
+ {
+ return internal_Step(Task, state, step, forceRefresh);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
new file mode 100644
index 0000000000..ddf3b829b5
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
@@ -0,0 +1,91 @@
+using System.Runtime.CompilerServices;
+
+namespace GodotTools.Internals
+{
+ public static class GodotSharpDirs
+ {
+ public static string ResDataDir => internal_ResDataDir();
+ public static string ResMetadataDir => internal_ResMetadataDir();
+ public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir();
+ public static string ResAssembliesDir => internal_ResAssembliesDir();
+ public static string ResConfigDir => internal_ResConfigDir();
+ public static string ResTempDir => internal_ResTempDir();
+ public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir();
+ public static string ResTempAssembliesDir => internal_ResTempAssembliesDir();
+
+ public static string MonoUserDir => internal_MonoUserDir();
+ public static string MonoLogsDir => internal_MonoLogsDir();
+
+ #region Tools-only
+ public static string MonoSolutionsDir => internal_MonoSolutionsDir();
+ public static string BuildLogsDirs => internal_BuildLogsDirs();
+
+ public static string ProjectSlnPath => internal_ProjectSlnPath();
+ public static string ProjectCsProjPath => internal_ProjectCsProjPath();
+
+ public static string DataEditorToolsDir => internal_DataEditorToolsDir();
+ public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir();
+ #endregion
+
+ public static string DataMonoEtcDir => internal_DataMonoEtcDir();
+ public static string DataMonoLibDir => internal_DataMonoLibDir();
+
+ #region Windows-only
+ public static string DataMonoBinDir => internal_DataMonoBinDir();
+ #endregion
+
+
+ #region Internal
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResDataDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResMetadataDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResAssembliesBaseDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResAssembliesDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResConfigDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResTempDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResTempAssembliesBaseDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ResTempAssembliesDir();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_MonoUserDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_MonoLogsDir();
+
+ #region Tools-only
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_MonoSolutionsDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_BuildLogsDirs();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ProjectSlnPath();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_ProjectCsProjPath();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_DataEditorToolsDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_DataEditorPrebuiltApiDir();
+ #endregion
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_DataMonoEtcDir();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_DataMonoLibDir();
+
+ #region Windows-only
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_DataMonoBinDir();
+ #endregion
+
+ #endregion
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
new file mode 100644
index 0000000000..5c7ce832cd
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Runtime.CompilerServices;
+using Godot;
+using Godot.Collections;
+
+namespace GodotTools.Internals
+{
+ public static class Internal
+ {
+ public const string CSharpLanguageType = "CSharpScript";
+ public const string CSharpLanguageExtension = "cs";
+
+ public static float EditorScale => internal_EditorScale();
+
+ public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) =>
+ internal_GlobalDef(setting, defaultValue, restartIfChanged);
+
+ public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) =>
+ internal_EditorDef(setting, defaultValue, restartIfChanged);
+
+ public static string FullTemplatesDir =>
+ internal_FullTemplatesDir();
+
+ public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path);
+
+ public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId);
+
+ public static bool MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType) =>
+ internal_MetadataIsApiAssemblyInvalidated(apiType);
+
+ public static void MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated) =>
+ internal_MetadataSetApiAssemblyInvalidated(apiType, invalidated);
+
+ public static bool IsMessageQueueFlushing() => internal_IsMessageQueueFlushing();
+
+ public static bool GodotIs32Bits() => internal_GodotIs32Bits();
+
+ public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble();
+
+ public static void GodotMainIteration() => internal_GodotMainIteration();
+
+ public static ulong GetCoreApiHash() => internal_GetCoreApiHash();
+
+ public static ulong GetEditorApiHash() => internal_GetEditorApiHash();
+
+ public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded();
+
+ public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload);
+
+ public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts();
+
+ public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) =>
+ internal_ScriptEditorEdit(resource, line, col, grabFocus);
+
+ public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
+
+ public static Dictionary<string, object> GetScriptsMetadataOrNothing() =>
+ internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>));
+
+ public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
+
+ // Internal Calls
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern float internal_EditorScale();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_FullTemplatesDir();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_SimplifyGodotPath(this string path);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_IsOsxAppBundleInstalled(string bundleId);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_IsMessageQueueFlushing();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_GodotIs32Bits();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_GodotIsRealTDouble();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_GodotMainIteration();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern ulong internal_GetCoreApiHash();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern ulong internal_GetEditorApiHash();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_IsAssembliesReloadingNeeded();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_ReloadAssemblies(bool softReload);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_ScriptEditorDebuggerReloadScripts();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_EditorNodeShowScriptScreen();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_MonoWindowsInstallRoot();
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
new file mode 100644
index 0000000000..2497d276a9
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Godot;
+using Godot.Collections;
+
+namespace GodotTools.Internals
+{
+ public static class ScriptClassParser
+ {
+ public class ClassDecl
+ {
+ public string Name { get; }
+ public string Namespace { get; }
+ public bool Nested { get; }
+ public int BaseCount { get; }
+
+ public ClassDecl(string name, string @namespace, bool nested, int baseCount)
+ {
+ Name = name;
+ Namespace = @namespace;
+ Nested = nested;
+ BaseCount = baseCount;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes);
+
+ public static void ParseFileOrThrow(string filePath, out IEnumerable<ClassDecl> classes)
+ {
+ var classesArray = new Array<Dictionary>();
+ var error = internal_ParseFile(filePath, classesArray);
+ if (error != Error.Ok)
+ throw new Exception($"Failed to determine namespace and class for script: {filePath}. Parse error: {error}");
+
+ var classesList = new List<ClassDecl>();
+
+ foreach (var classDeclDict in classesArray)
+ {
+ classesList.Add(new ClassDecl(
+ (string) classDeclDict["name"],
+ (string) classDeclDict["namespace"],
+ (bool) classDeclDict["nested"],
+ (int) classDeclDict["base_count"]
+ ));
+ }
+
+ classes = classesList;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs
new file mode 100644
index 0000000000..300cf7fcb9
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs
@@ -0,0 +1,342 @@
+using Godot;
+using System;
+using System.IO;
+using Godot.Collections;
+using GodotTools.Internals;
+using File = GodotTools.Utils.File;
+using Path = System.IO.Path;
+
+namespace GodotTools
+{
+ public class MonoBottomPanel : VBoxContainer
+ {
+ private EditorInterface editorInterface;
+
+ private TabContainer panelTabs;
+
+ private VBoxContainer panelBuildsTab;
+
+ private ItemList buildTabsList;
+ private TabContainer buildTabs;
+
+ private ToolButton warningsBtn;
+ private ToolButton errorsBtn;
+ private Button viewLogBtn;
+
+ private void _UpdateBuildTabsList()
+ {
+ buildTabsList.Clear();
+
+ int currentTab = buildTabs.CurrentTab;
+
+ bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
+
+ for (int i = 0; i < buildTabs.GetChildCount(); i++)
+ {
+ var tab = (MonoBuildTab) buildTabs.GetChild(i);
+
+ if (tab == null)
+ continue;
+
+ string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
+ itemName += " [" + tab.BuildInfo.Configuration + "]";
+
+ buildTabsList.AddItem(itemName, tab.IconTexture);
+
+ string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
+ itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
+ itemTooltip += "\nStatus: ";
+
+ if (tab.BuildExited)
+ itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored";
+ else
+ itemTooltip += "Running";
+
+ if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error)
+ itemTooltip += $"\nErrors: {tab.ErrorCount}";
+
+ itemTooltip += $"\nWarnings: {tab.WarningCount}";
+
+ buildTabsList.SetItemTooltip(i, itemTooltip);
+
+ if (noCurrentTab || currentTab == i)
+ {
+ buildTabsList.Select(i);
+ _BuildTabsItemSelected(i);
+ }
+ }
+ }
+
+ public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo)
+ {
+ foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren()))
+ {
+ if (buildTab.BuildInfo.Equals(buildInfo))
+ return buildTab;
+ }
+
+ var newBuildTab = new MonoBuildTab(buildInfo);
+ AddBuildTab(newBuildTab);
+
+ return newBuildTab;
+ }
+
+ private void _BuildTabsItemSelected(int idx)
+ {
+ if (idx < 0 || idx >= buildTabs.GetTabCount())
+ throw new IndexOutOfRangeException();
+
+ buildTabs.CurrentTab = idx;
+ if (!buildTabs.Visible)
+ buildTabs.Visible = true;
+
+ warningsBtn.Visible = true;
+ errorsBtn.Visible = true;
+ viewLogBtn.Visible = true;
+ }
+
+ private void _BuildTabsNothingSelected()
+ {
+ if (buildTabs.GetTabCount() != 0)
+ {
+ // just in case
+ buildTabs.Visible = false;
+
+ // This callback is called when clicking on the empty space of the list.
+ // ItemList won't deselect the items automatically, so we must do it ourselves.
+ buildTabsList.UnselectAll();
+ }
+
+ warningsBtn.Visible = false;
+ errorsBtn.Visible = false;
+ viewLogBtn.Visible = false;
+ }
+
+ private void _WarningsToggled(bool pressed)
+ {
+ int currentTab = buildTabs.CurrentTab;
+
+ if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+ throw new InvalidOperationException("No tab selected");
+
+ var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
+ buildTab.WarningsVisible = pressed;
+ buildTab.UpdateIssuesList();
+ }
+
+ private void _ErrorsToggled(bool pressed)
+ {
+ int currentTab = buildTabs.CurrentTab;
+
+ if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+ throw new InvalidOperationException("No tab selected");
+
+ var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
+ buildTab.ErrorsVisible = pressed;
+ buildTab.UpdateIssuesList();
+ }
+
+ public void BuildProjectPressed()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return; // No solution to build
+
+ string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
+ string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
+
+ CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
+
+ if (File.Exists(editorScriptsMetadataPath))
+ {
+ try
+ {
+ File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
+ }
+ catch (IOException e)
+ {
+ GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}");
+ return;
+ }
+ }
+
+ var godotDefines = new[]
+ {
+ OS.GetName(),
+ Internal.GodotIs32Bits() ? "32" : "64"
+ };
+
+ bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines);
+
+ if (!buildSuccess)
+ return;
+
+ // Notify running game for hot-reload
+ Internal.ScriptEditorDebuggerReloadScripts();
+
+ // Hot-reload in the editor
+ GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
+
+ if (Internal.IsAssembliesReloadingNeeded())
+ Internal.ReloadAssemblies(softReload: false);
+ }
+
+ private void _ViewLogPressed()
+ {
+ if (!buildTabsList.IsAnythingSelected())
+ return;
+
+ var selectedItems = buildTabsList.GetSelectedItems();
+
+ if (selectedItems.Length != 1)
+ throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}");
+
+ int selectedItem = selectedItems[0];
+
+ var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem);
+
+ OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName));
+ }
+
+ public override void _Notification(int what)
+ {
+ base._Notification(what);
+
+ if (what == EditorSettings.NotificationEditorSettingsChanged)
+ {
+ var editorBaseControl = editorInterface.GetBaseControl();
+ panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
+ }
+ }
+
+ public void AddBuildTab(MonoBuildTab buildTab)
+ {
+ buildTabs.AddChild(buildTab);
+ RaiseBuildTab(buildTab);
+ }
+
+ public void RaiseBuildTab(MonoBuildTab buildTab)
+ {
+ if (buildTab.GetParent() != buildTabs)
+ throw new InvalidOperationException("Build tab is not in the tabs list");
+
+ buildTabs.MoveChild(buildTab, 0);
+ _UpdateBuildTabsList();
+ }
+
+ public void ShowBuildTab()
+ {
+ for (int i = 0; i < panelTabs.GetTabCount(); i++)
+ {
+ if (panelTabs.GetTabControl(i) == panelBuildsTab)
+ {
+ panelTabs.CurrentTab = i;
+ GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this);
+ return;
+ }
+ }
+
+ GD.PushError("Builds tab not found");
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ editorInterface = GodotSharpEditor.Instance.GetEditorInterface();
+
+ var editorBaseControl = editorInterface.GetBaseControl();
+
+ SizeFlagsVertical = (int) SizeFlags.ExpandFill;
+ SetAnchorsAndMarginsPreset(LayoutPreset.Wide);
+
+ panelTabs = new TabContainer
+ {
+ TabAlign = TabContainer.TabAlignEnum.Left,
+ RectMinSize = new Vector2(0, 228) * Internal.EditorScale,
+ SizeFlagsVertical = (int) SizeFlags.ExpandFill
+ };
+ panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
+ AddChild(panelTabs);
+
+ {
+ // Builds tab
+ panelBuildsTab = new VBoxContainer
+ {
+ Name = "Builds", // TTR
+ SizeFlagsHorizontal = (int) SizeFlags.ExpandFill
+ };
+ panelTabs.AddChild(panelBuildsTab);
+
+ var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
+ panelBuildsTab.AddChild(toolBarHBox);
+
+ var buildProjectBtn = new Button
+ {
+ Text = "Build Project", // TTR
+ FocusMode = FocusModeEnum.None
+ };
+ buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed));
+ toolBarHBox.AddChild(buildProjectBtn);
+
+ toolBarHBox.AddSpacer(begin: false);
+
+ warningsBtn = new ToolButton
+ {
+ Text = "Warnings", // TTR
+ ToggleMode = true,
+ Pressed = true,
+ Visible = false,
+ FocusMode = FocusModeEnum.None
+ };
+ warningsBtn.Connect("toggled", this, nameof(_WarningsToggled));
+ toolBarHBox.AddChild(warningsBtn);
+
+ errorsBtn = new ToolButton
+ {
+ Text = "Errors", // TTR
+ ToggleMode = true,
+ Pressed = true,
+ Visible = false,
+ FocusMode = FocusModeEnum.None
+ };
+ errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled));
+ toolBarHBox.AddChild(errorsBtn);
+
+ toolBarHBox.AddSpacer(begin: false);
+
+ viewLogBtn = new Button
+ {
+ Text = "View log", // TTR
+ FocusMode = FocusModeEnum.None,
+ Visible = false
+ };
+ viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed));
+ toolBarHBox.AddChild(viewLogBtn);
+
+ var hsc = new HSplitContainer
+ {
+ SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
+ SizeFlagsVertical = (int) SizeFlags.ExpandFill
+ };
+ panelBuildsTab.AddChild(hsc);
+
+ buildTabsList = new ItemList {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
+ buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected));
+ buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected));
+ hsc.AddChild(buildTabsList);
+
+ buildTabs = new TabContainer
+ {
+ TabAlign = TabContainer.TabAlignEnum.Left,
+ SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
+ TabsVisible = false
+ };
+ hsc.AddChild(buildTabs);
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs
new file mode 100644
index 0000000000..858e852392
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs
@@ -0,0 +1,47 @@
+using System;
+using Godot;
+using Godot.Collections;
+using GodotTools.Internals;
+using Path = System.IO.Path;
+
+namespace GodotTools
+{
+ [Serializable]
+ public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization
+ {
+ public string Solution { get; }
+ public string Configuration { 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}");
+
+ public override bool Equals(object obj)
+ {
+ if (obj is MonoBuildInfo other)
+ return other.Solution == Solution && other.Configuration == Configuration;
+
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 29 + Solution.GetHashCode();
+ hash = hash * 29 + Configuration.GetHashCode();
+ return hash;
+ }
+ }
+
+ private MonoBuildInfo()
+ {
+ }
+
+ public MonoBuildInfo(string solution, string configuration)
+ {
+ Solution = solution;
+ Configuration = configuration;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs
new file mode 100644
index 0000000000..75fdacc0da
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs
@@ -0,0 +1,260 @@
+using Godot;
+using System;
+using Godot.Collections;
+using GodotTools.Internals;
+using File = GodotTools.Utils.File;
+using Path = System.IO.Path;
+
+namespace GodotTools
+{
+ public class MonoBuildTab : VBoxContainer
+ {
+ public enum BuildResults
+ {
+ Error,
+ Success
+ }
+
+ [Serializable]
+ private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization
+ {
+ public bool Warning { get; set; }
+ public string File { get; set; }
+ public int Line { get; set; }
+ public int Column { get; set; }
+ public string Code { get; set; }
+ public string Message { get; set; }
+ public string ProjectFile { get; set; }
+ }
+
+ private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
+ private ItemList issuesList;
+
+ public bool BuildExited { get; private set; } = false;
+
+ public BuildResults? BuildResult { get; private set; } = null;
+
+ public int ErrorCount { get; private set; } = 0;
+
+ public int WarningCount { get; private set; } = 0;
+
+ public bool ErrorsVisible { get; set; } = true;
+ public bool WarningsVisible { get; set; } = true;
+
+ public Texture IconTexture
+ {
+ get
+ {
+ if (!BuildExited)
+ return GetIcon("Stop", "EditorIcons");
+
+ if (BuildResult == BuildResults.Error)
+ return GetIcon("StatusError", "EditorIcons");
+
+ return GetIcon("StatusSuccess", "EditorIcons");
+ }
+ }
+
+ public MonoBuildInfo BuildInfo { get; private set; }
+
+ private void _LoadIssuesFromFile(string csvFile)
+ {
+ using (var file = new Godot.File())
+ {
+ Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read);
+
+ if (openError != Error.Ok)
+ return;
+
+ while (!file.EofReached())
+ {
+ string[] csvColumns = file.GetCsvLine();
+
+ if (csvColumns.Length == 1 && csvColumns[0].Empty())
+ return;
+
+ if (csvColumns.Length != 7)
+ {
+ GD.PushError($"Expected 7 columns, got {csvColumns.Length}");
+ continue;
+ }
+
+ var issue = new BuildIssue
+ {
+ Warning = csvColumns[0] == "warning",
+ File = csvColumns[1],
+ Line = int.Parse(csvColumns[2]),
+ Column = int.Parse(csvColumns[3]),
+ Code = csvColumns[4],
+ Message = csvColumns[5],
+ ProjectFile = csvColumns[6]
+ };
+
+ if (issue.Warning)
+ WarningCount += 1;
+ else
+ ErrorCount += 1;
+
+ issues.Add(issue);
+ }
+ }
+ }
+
+ private void _IssueActivated(int idx)
+ {
+ if (idx < 0 || idx >= issuesList.GetItemCount())
+ throw new IndexOutOfRangeException("Item list index out of range");
+
+ // Get correct issue idx from issue list
+ int issueIndex = (int) issuesList.GetItemMetadata(idx);
+
+ if (idx < 0 || idx >= issues.Count)
+ throw new IndexOutOfRangeException("Issue index out of range");
+
+ BuildIssue issue = issues[issueIndex];
+
+ if (issue.ProjectFile.Empty() && issue.File.Empty())
+ return;
+
+ string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
+
+ string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
+
+ if (!File.Exists(file))
+ return;
+
+ file = ProjectSettings.LocalizePath(file);
+
+ if (file.StartsWith("res://"))
+ {
+ var script = (Script) ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
+
+ if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
+ Internal.EditorNodeShowScriptScreen();
+ }
+ }
+
+ public void UpdateIssuesList()
+ {
+ issuesList.Clear();
+
+ using (var warningIcon = GetIcon("Warning", "EditorIcons"))
+ using (var errorIcon = GetIcon("Error", "EditorIcons"))
+ {
+ for (int i = 0; i < issues.Count; i++)
+ {
+ BuildIssue issue = issues[i];
+
+ if (!(issue.Warning ? WarningsVisible : ErrorsVisible))
+ continue;
+
+ string tooltip = string.Empty;
+ tooltip += $"Message: {issue.Message}";
+
+ if (!issue.Code.Empty())
+ tooltip += $"\nCode: {issue.Code}";
+
+ tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
+
+ string text = string.Empty;
+
+ if (!issue.File.Empty())
+ {
+ text += $"{issue.File}({issue.Line},{issue.Column}): ";
+
+ tooltip += $"\nFile: {issue.File}";
+ tooltip += $"\nLine: {issue.Line}";
+ tooltip += $"\nColumn: {issue.Column}";
+ }
+
+ if (!issue.ProjectFile.Empty())
+ tooltip += $"\nProject: {issue.ProjectFile}";
+
+ text += issue.Message;
+
+ int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal);
+ string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx);
+ issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon);
+
+ int index = issuesList.GetItemCount() - 1;
+ issuesList.SetItemTooltip(index, tooltip);
+ issuesList.SetItemMetadata(index, i);
+ }
+ }
+ }
+
+ public void OnBuildStart()
+ {
+ BuildExited = false;
+
+ issues.Clear();
+ WarningCount = 0;
+ ErrorCount = 0;
+ UpdateIssuesList();
+
+ GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
+ }
+
+ public void OnBuildExit(BuildResults result)
+ {
+ BuildExited = true;
+ BuildResult = result;
+
+ _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName));
+ UpdateIssuesList();
+
+ GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
+ }
+
+ public void OnBuildExecFailed(string cause)
+ {
+ BuildExited = true;
+ BuildResult = BuildResults.Error;
+
+ issuesList.Clear();
+
+ var issue = new BuildIssue {Message = cause, Warning = false};
+
+ ErrorCount += 1;
+ issues.Add(issue);
+
+ UpdateIssuesList();
+
+ GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
+ }
+
+ public void RestartBuild()
+ {
+ if (!BuildExited)
+ throw new InvalidOperationException("Build already started");
+
+ GodotSharpBuilds.RestartBuild(this);
+ }
+
+ public void StopBuild()
+ {
+ if (!BuildExited)
+ throw new InvalidOperationException("Build is not in progress");
+
+ GodotSharpBuilds.StopBuild(this);
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ issuesList = new ItemList {SizeFlagsVertical = (int) SizeFlags.ExpandFill};
+ issuesList.Connect("item_activated", this, nameof(_IssueActivated));
+ AddChild(issuesList);
+ }
+
+ private MonoBuildTab()
+ {
+ }
+
+ public MonoBuildTab(MonoBuildInfo buildInfo)
+ {
+ BuildInfo = buildInfo;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs
index fba4a8f65c..0c8d86e799 100644
--- a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs
@@ -1,11 +1,11 @@
+using GodotTools.Core;
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Runtime.CompilerServices;
+using GodotTools.Internals;
-namespace GodotSharpTools.Editor
+namespace GodotTools
{
public class MonoDevelopInstance
{
@@ -15,24 +15,24 @@ namespace GodotSharpTools.Editor
VisualStudioForMac = 1
}
- readonly string solutionFile;
- readonly EditorId editorId;
+ private readonly string solutionFile;
+ private readonly EditorId editorId;
- Process process;
+ private Process process;
- public void Execute(string[] files)
+ public void Execute(params string[] files)
{
bool newWindow = process == null || process.HasExited;
- List<string> args = new List<string>();
+ var args = new List<string>();
string command;
if (Utils.OS.IsOSX())
{
- string bundleId = codeEditorBundleIds[editorId];
+ string bundleId = CodeEditorBundleIds[editorId];
- if (IsApplicationBundleInstalled(bundleId))
+ if (Internal.IsOsxAppBundleInstalled(bundleId))
{
command = "open";
@@ -47,12 +47,12 @@ namespace GodotSharpTools.Editor
}
else
{
- command = codeEditorPaths[editorId];
+ command = CodeEditorPaths[editorId];
}
}
else
{
- command = codeEditorPaths[editorId];
+ command = CodeEditorPaths[editorId];
}
args.Add("--ipc-tcp");
@@ -72,7 +72,7 @@ namespace GodotSharpTools.Editor
if (newWindow)
{
- process = Process.Start(new ProcessStartInfo()
+ process = Process.Start(new ProcessStartInfo
{
FileName = command,
Arguments = string.Join(" ", args),
@@ -81,12 +81,12 @@ namespace GodotSharpTools.Editor
}
else
{
- Process.Start(new ProcessStartInfo()
+ Process.Start(new ProcessStartInfo
{
FileName = command,
Arguments = string.Join(" ", args),
UseShellExecute = false
- });
+ })?.Dispose();
}
}
@@ -99,45 +99,42 @@ namespace GodotSharpTools.Editor
this.editorId = editorId;
}
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static bool IsApplicationBundleInstalled(string bundleId);
-
- static readonly IReadOnlyDictionary<EditorId, string> codeEditorPaths;
- static readonly IReadOnlyDictionary<EditorId, string> codeEditorBundleIds;
+ private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorPaths;
+ private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorBundleIds;
static MonoDevelopInstance()
{
if (Utils.OS.IsOSX())
{
- codeEditorPaths = new Dictionary<EditorId, string>
+ CodeEditorPaths = new Dictionary<EditorId, string>
{
// Rely on PATH
- { EditorId.MonoDevelop, "monodevelop" },
- { EditorId.VisualStudioForMac, "VisualStudio" }
+ {EditorId.MonoDevelop, "monodevelop"},
+ {EditorId.VisualStudioForMac, "VisualStudio"}
};
- codeEditorBundleIds = new Dictionary<EditorId, string>
+ CodeEditorBundleIds = new Dictionary<EditorId, string>
{
// TODO EditorId.MonoDevelop
- { EditorId.VisualStudioForMac, "com.microsoft.visual-studio" }
+ {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
};
}
else if (Utils.OS.IsWindows())
{
- codeEditorPaths = new Dictionary<EditorId, string>
+ CodeEditorPaths = new Dictionary<EditorId, string>
{
// XamarinStudio is no longer a thing, and the latest version is quite old
// MonoDevelop is available from source only on Windows. The recommendation
// is to use Visual Studio instead. Since there are no official builds, we
// will rely on custom MonoDevelop builds being added to PATH.
- { EditorId.MonoDevelop, "MonoDevelop.exe" }
+ {EditorId.MonoDevelop, "MonoDevelop.exe"}
};
}
else if (Utils.OS.IsUnix())
{
- codeEditorPaths = new Dictionary<EditorId, string>
+ CodeEditorPaths = new Dictionary<EditorId, string>
{
// Rely on PATH
- { EditorId.MonoDevelop, "monodevelop" }
+ {EditorId.MonoDevelop, "monodevelop"}
};
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..f5fe85c722
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+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/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs
new file mode 100644
index 0000000000..3ae6c10bbf
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+
+namespace GodotTools.Utils
+{
+ public static class CollectionExtensions
+ {
+ public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null)
+ where T : class
+ {
+ foreach (T elem in enumerable)
+ {
+ if (predicate(elem) != null)
+ return elem;
+ }
+
+ return orElse;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs
new file mode 100644
index 0000000000..c67d48b92a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs
@@ -0,0 +1,40 @@
+using System.IO;
+using Godot;
+
+namespace GodotTools.Utils
+{
+ public static class Directory
+ {
+ private static string GlobalizePath(this string path)
+ {
+ return ProjectSettings.GlobalizePath(path);
+ }
+
+ public static bool Exists(string path)
+ {
+ return System.IO.Directory.Exists(path.GlobalizePath());
+ }
+
+ /// Create directory recursively
+ public static DirectoryInfo CreateDirectory(string path)
+ {
+ return System.IO.Directory.CreateDirectory(path.GlobalizePath());
+ }
+
+ public static void Delete(string path, bool recursive)
+ {
+ System.IO.Directory.Delete(path.GlobalizePath(), recursive);
+ }
+
+
+ public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
+ {
+ return System.IO.Directory.GetDirectories(path.GlobalizePath(), searchPattern, searchOption);
+ }
+
+ public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
+ {
+ return System.IO.Directory.GetFiles(path.GlobalizePath(), searchPattern, searchOption);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs
new file mode 100644
index 0000000000..e1e2188edb
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs
@@ -0,0 +1,43 @@
+using System;
+using Godot;
+
+namespace GodotTools.Utils
+{
+ public static class File
+ {
+ private static string GlobalizePath(this string path)
+ {
+ return ProjectSettings.GlobalizePath(path);
+ }
+
+ public static void WriteAllText(string path, string contents)
+ {
+ System.IO.File.WriteAllText(path.GlobalizePath(), contents);
+ }
+
+ public static bool Exists(string path)
+ {
+ return System.IO.File.Exists(path.GlobalizePath());
+ }
+
+ public static DateTime GetLastWriteTime(string path)
+ {
+ return System.IO.File.GetLastWriteTime(path.GlobalizePath());
+ }
+
+ public static void Delete(string path)
+ {
+ System.IO.File.Delete(path.GlobalizePath());
+ }
+
+ public static void Copy(string sourceFileName, string destFileName)
+ {
+ System.IO.File.Copy(sourceFileName.GlobalizePath(), destFileName.GlobalizePath(), overwrite: true);
+ }
+
+ public static byte[] ReadAllBytes(string path)
+ {
+ return System.IO.File.ReadAllBytes(path.GlobalizePath());
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
new file mode 100644
index 0000000000..e48b1115db
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace GodotTools.Utils
+{
+ public static class OS
+ {
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ extern static string GetPlatformName();
+
+ const string HaikuName = "Haiku";
+ const string OSXName = "OSX";
+ const string ServerName = "Server";
+ const string UWPName = "UWP";
+ const string WindowsName = "Windows";
+ const string X11Name = "X11";
+
+ public static bool IsHaiku()
+ {
+ return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool IsOSX()
+ {
+ return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool IsServer()
+ {
+ return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool IsUWP()
+ {
+ return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool IsWindows()
+ {
+ return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool IsX11()
+ {
+ return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool? _isUnixCache;
+ private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name};
+
+ public static bool IsUnix()
+ {
+ if (_isUnixCache.HasValue)
+ return _isUnixCache.Value;
+
+ string osName = GetPlatformName();
+ _isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
+ return _isUnixCache.Value;
+ }
+
+ public static char PathSep => IsWindows() ? ';' : ':';
+
+ public static string PathWhich(string name)
+ {
+ string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null;
+ string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
+
+ var searchDirs = new List<string>();
+
+ if (pathDirs != null)
+ searchDirs.AddRange(pathDirs);
+
+ searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list
+
+ foreach (var dir in searchDirs)
+ {
+ string path = Path.Combine(dir, name);
+
+ if (IsWindows() && windowsExts != null)
+ {
+ foreach (var extension in windowsExts)
+ {
+ string pathWithExtension = path + extension;
+
+ if (File.Exists(pathWithExtension))
+ return pathWithExtension;
+ }
+ }
+ else
+ {
+ if (File.Exists(path))
+ return path;
+ }
+ }
+
+ return null;
+ }
+
+ public static void RunProcess(string command, IEnumerable<string> arguments)
+ {
+ string CmdLineArgsToString(IEnumerable<string> args)
+ {
+ return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
+ }
+
+ ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments))
+ {
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false
+ };
+
+ using (Process process = Process.Start(startInfo))
+ {
+ if (process == null)
+ throw new Exception("No process was started");
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index fe7ced060d..45037bf637 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -38,7 +38,6 @@
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
-#include "core/string_builder.h"
#include "core/ucaps.h"
#include "../glue/cs_compressed.gen.h"
@@ -98,14 +97,10 @@
#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type
#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array"
-#define BINDINGS_GENERATOR_VERSION UINT32_C(8)
+#define BINDINGS_GENERATOR_VERSION UINT32_C(9)
const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n");
-bool BindingsGenerator::verbose_output = false;
-
-BindingsGenerator *BindingsGenerator::singleton = NULL;
-
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.
@@ -757,47 +752,47 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) {
}
}
-void BindingsGenerator::_generate_global_constants(List<String> &p_output) {
+void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
// Constants (in partial GD class)
- p_output.push_back("\n#pragma warning disable CS1591 // Disable warning: "
- "'Missing XML comment for publicly visible type or member'\n");
+ p_output.append("\n#pragma warning disable CS1591 // Disable warning: "
+ "'Missing XML comment for publicly visible type or member'\n");
- p_output.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
- p_output.push_back(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{");
+ p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
+ p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{");
for (const List<ConstantInterface>::Element *E = global_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), NULL);
- Vector<String> summary_lines = xml_summary.split("\n");
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
- p_output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
for (int i = 0; i < summary_lines.size(); i++) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(summary_lines[i]);
- p_output.push_back("\n");
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- p_output.push_back(INDENT2 "/// </summary>");
+ p_output.append(INDENT2 "/// </summary>");
}
}
- p_output.push_back(MEMBER_BEGIN "public const int ");
- p_output.push_back(iconstant.proxy_name);
- p_output.push_back(" = ");
- p_output.push_back(itos(iconstant.value));
- p_output.push_back(";");
+ p_output.append(MEMBER_BEGIN "public const int ");
+ p_output.append(iconstant.proxy_name);
+ p_output.append(" = ");
+ p_output.append(itos(iconstant.value));
+ p_output.append(";");
}
if (!global_constants.empty())
- p_output.push_back("\n");
+ p_output.append("\n");
- p_output.push_back(INDENT1 CLOSE_BLOCK); // end of GD class
+ p_output.append(INDENT1 CLOSE_BLOCK); // end of GD class
// Enums
@@ -817,90 +812,82 @@ void BindingsGenerator::_generate_global_constants(List<String> &p_output) {
CRASH_COND(enum_class_name != "Variant"); // Hard-coded...
- if (verbose_output) {
- WARN_PRINTS("Declaring global enum `" + enum_proxy_name + "` inside static class `" + enum_class_name + "`");
- }
+ _log("Declaring global enum `%s` inside static class `%s`\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data());
- p_output.push_back("\n" INDENT1 "public static partial class ");
- p_output.push_back(enum_class_name);
- p_output.push_back("\n" INDENT1 OPEN_BLOCK);
+ p_output.append("\n" INDENT1 "public static partial class ");
+ p_output.append(enum_class_name);
+ p_output.append("\n" INDENT1 OPEN_BLOCK);
}
- p_output.push_back("\n" INDENT1 "public enum ");
- p_output.push_back(enum_proxy_name);
- p_output.push_back("\n" INDENT1 OPEN_BLOCK);
+ p_output.append("\n" INDENT1 "public enum ");
+ p_output.append(enum_proxy_name);
+ p_output.append("\n" INDENT1 OPEN_BLOCK);
for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) {
const ConstantInterface &iconstant = F->get();
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL);
- Vector<String> summary_lines = xml_summary.split("\n");
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
- p_output.push_back(INDENT2 "/// <summary>\n");
+ p_output.append(INDENT2 "/// <summary>\n");
for (int i = 0; i < summary_lines.size(); i++) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(summary_lines[i]);
- p_output.push_back("\n");
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- p_output.push_back(INDENT2 "/// </summary>\n");
+ p_output.append(INDENT2 "/// </summary>\n");
}
}
- p_output.push_back(INDENT2);
- p_output.push_back(iconstant.proxy_name);
- p_output.push_back(" = ");
- p_output.push_back(itos(iconstant.value));
- p_output.push_back(F != ienum.constants.back() ? ",\n" : "\n");
+ p_output.append(INDENT2);
+ p_output.append(iconstant.proxy_name);
+ p_output.append(" = ");
+ p_output.append(itos(iconstant.value));
+ p_output.append(F != ienum.constants.back() ? ",\n" : "\n");
}
- p_output.push_back(INDENT1 CLOSE_BLOCK);
+ p_output.append(INDENT1 CLOSE_BLOCK);
if (enum_in_static_class)
- p_output.push_back(INDENT1 CLOSE_BLOCK);
+ p_output.append(INDENT1 CLOSE_BLOCK);
}
- p_output.push_back(CLOSE_BLOCK); // end of namespace
+ p_output.append(CLOSE_BLOCK); // end of namespace
- p_output.push_back("\n#pragma warning restore CS1591\n");
+ p_output.append("\n#pragma warning restore CS1591\n");
}
-Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) {
-
- verbose_output = p_verbose_output;
-
- String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME);
+Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
- if (!DirAccess::exists(proj_dir)) {
- Error err = da->make_dir_recursive(proj_dir);
+ if (!DirAccess::exists(p_proj_dir)) {
+ Error err = da->make_dir_recursive(p_proj_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
- da->change_dir(proj_dir);
+ da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
- String core_dir = path_join(proj_dir, "Core");
- String obj_type_dir = path_join(proj_dir, "ObjectType");
-
- Vector<String> compile_items;
+ String core_dir = path::join(p_proj_dir, "Core");
+ String obj_type_dir = path::join(p_proj_dir, "ObjectType");
// Generate source file for global scope constants and enums
{
- List<String> constants_source;
+ StringBuilder constants_source;
_generate_global_constants(constants_source);
- String output_file = path_join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");
+ String output_file = path::join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");
Error save_err = _save_file(output_file, constants_source);
if (save_err != OK)
return save_err;
- compile_items.push_back(output_file);
+ r_compile_items.push_back(output_file);
}
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
@@ -909,7 +896,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (itype.api_type == ClassDB::API_EDITOR)
continue;
- String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs");
+ String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs");
Error err = _generate_cs_type(itype, output_file);
if (err == ERR_SKIP)
@@ -918,19 +905,19 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (err != OK)
return err;
- compile_items.push_back(output_file);
+ r_compile_items.push_back(output_file);
}
// Generate sources from compressed files
- Map<String, CompressedFile> compressed_files;
+ Map<String, GodotCsCompressedFile> compressed_files;
get_compressed_files(compressed_files);
- for (Map<String, CompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) {
+ for (Map<String, GodotCsCompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) {
const String &file_name = E->key();
- const CompressedFile &file_data = E->value();
+ const GodotCsCompressedFile &file_data = E->value();
- String output_file = path_join(core_dir, file_name);
+ String output_file = path::join(core_dir, file_name);
Vector<uint8_t> data;
data.resize(file_data.uncompressed_size);
@@ -948,31 +935,31 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
file->store_buffer(data.ptr(), data.size());
file->close();
- compile_items.push_back(output_file);
+ r_compile_items.push_back(output_file);
}
- List<String> cs_icalls_content;
-
- cs_icalls_content.push_back("using System;\n"
- "using System.Runtime.CompilerServices;\n"
- "\n");
- cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
- cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK);
-
- cs_icalls_content.push_back(MEMBER_BEGIN "internal static ulong godot_api_hash = ");
- cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n");
- cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint bindings_version = ");
- cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n");
- cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint cs_glue_version = ");
- cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n");
-
-#define ADD_INTERNAL_CALL(m_icall) \
- if (!m_icall.editor_only) { \
- cs_icalls_content.push_back(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \
- cs_icalls_content.push_back(INDENT2 "internal extern static "); \
- cs_icalls_content.push_back(m_icall.im_type_out + " "); \
- cs_icalls_content.push_back(m_icall.name + "("); \
- cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \
+ StringBuilder cs_icalls_content;
+
+ cs_icalls_content.append("using System;\n"
+ "using System.Runtime.CompilerServices;\n"
+ "\n");
+ cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
+ cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK);
+
+ cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = ");
+ cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n");
+ cs_icalls_content.append(MEMBER_BEGIN "internal static uint bindings_version = ");
+ cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n");
+ cs_icalls_content.append(MEMBER_BEGIN "internal static uint cs_glue_version = ");
+ cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n");
+
+#define ADD_INTERNAL_CALL(m_icall) \
+ if (!m_icall.editor_only) { \
+ cs_icalls_content.append(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \
+ cs_icalls_content.append(INDENT2 "internal extern static "); \
+ cs_icalls_content.append(m_icall.im_type_out + " "); \
+ cs_icalls_content.append(m_icall.name + "("); \
+ cs_icalls_content.append(m_icall.im_sig + ");\n"); \
}
for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next())
@@ -982,54 +969,35 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
#undef ADD_INTERNAL_CALL
- cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
+ cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
- String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs");
+ String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs");
Error err = _save_file(internal_methods_file, cs_icalls_content);
if (err != OK)
return err;
- compile_items.push_back(internal_methods_file);
-
- String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items);
-
- DotNetSolution::ProjectInfo proj_info;
- proj_info.guid = guid;
- proj_info.relpath = String(CORE_API_ASSEMBLY_NAME).plus_file(CORE_API_ASSEMBLY_NAME ".csproj");
- proj_info.configs.push_back("Debug");
- proj_info.configs.push_back("Release");
-
- r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info);
-
- if (verbose_output)
- OS::get_singleton()->print("The solution and C# project for the Core API was generated successfully\n");
+ r_compile_items.push_back(internal_methods_file);
return OK;
}
-Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) {
-
- verbose_output = p_verbose_output;
-
- String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME);
+Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
- if (!DirAccess::exists(proj_dir)) {
- Error err = da->make_dir_recursive(proj_dir);
+ if (!DirAccess::exists(p_proj_dir)) {
+ Error err = da->make_dir_recursive(p_proj_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
- da->change_dir(proj_dir);
+ da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
- String core_dir = path_join(proj_dir, "Core");
- String obj_type_dir = path_join(proj_dir, "ObjectType");
-
- Vector<String> compile_items;
+ String core_dir = path::join(p_proj_dir, "Core");
+ String obj_type_dir = path::join(p_proj_dir, "ObjectType");
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
const TypeInterface &itype = E.get();
@@ -1037,7 +1005,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir
if (itype.api_type != ClassDB::API_EDITOR)
continue;
- String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs");
+ String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs");
Error err = _generate_cs_type(itype, output_file);
if (err == ERR_SKIP)
@@ -1046,32 +1014,32 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir
if (err != OK)
return err;
- compile_items.push_back(output_file);
+ r_compile_items.push_back(output_file);
}
- List<String> cs_icalls_content;
-
- cs_icalls_content.push_back("using System;\n"
- "using System.Runtime.CompilerServices;\n"
- "\n");
- cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
- cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK);
-
- cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = ");
- cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n");
- cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = ");
- cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n");
- cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = ");
- cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n");
- cs_icalls_content.push_back("\n");
-
-#define ADD_INTERNAL_CALL(m_icall) \
- if (m_icall.editor_only) { \
- cs_icalls_content.push_back(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \
- cs_icalls_content.push_back(INDENT2 "internal extern static "); \
- cs_icalls_content.push_back(m_icall.im_type_out + " "); \
- cs_icalls_content.push_back(m_icall.name + "("); \
- cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \
+ StringBuilder cs_icalls_content;
+
+ cs_icalls_content.append("using System;\n"
+ "using System.Runtime.CompilerServices;\n"
+ "\n");
+ cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
+ cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK);
+
+ cs_icalls_content.append(INDENT2 "internal static ulong godot_api_hash = ");
+ cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n");
+ cs_icalls_content.append(INDENT2 "internal static uint bindings_version = ");
+ cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n");
+ cs_icalls_content.append(INDENT2 "internal static uint cs_glue_version = ");
+ cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n");
+ cs_icalls_content.append("\n");
+
+#define ADD_INTERNAL_CALL(m_icall) \
+ if (m_icall.editor_only) { \
+ cs_icalls_content.append(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \
+ cs_icalls_content.append(INDENT2 "internal extern static "); \
+ cs_icalls_content.append(m_icall.im_type_out + " "); \
+ cs_icalls_content.append(m_icall.name + "("); \
+ cs_icalls_content.append(m_icall.im_sig + ");\n"); \
}
for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next())
@@ -1081,67 +1049,64 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir
#undef ADD_INTERNAL_CALL
- cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
+ cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
- String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");
+ String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");
Error err = _save_file(internal_methods_file, cs_icalls_content);
if (err != OK)
return err;
- compile_items.push_back(internal_methods_file);
-
- String guid = CSharpProject::generate_editor_api_project(proj_dir, "../" CORE_API_ASSEMBLY_NAME "/" CORE_API_ASSEMBLY_NAME ".csproj", compile_items);
-
- DotNetSolution::ProjectInfo proj_info;
- proj_info.guid = guid;
- proj_info.relpath = String(EDITOR_API_ASSEMBLY_NAME).plus_file(EDITOR_API_ASSEMBLY_NAME ".csproj");
- proj_info.configs.push_back("Debug");
- proj_info.configs.push_back("Release");
-
- r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info);
-
- if (verbose_output)
- OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n");
+ r_compile_items.push_back(internal_methods_file);
return OK;
}
-Error BindingsGenerator::generate_cs_api(const String &p_output_dir, bool p_verbose_output) {
+Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
+
+ String output_dir = path::abspath(path::realpath(p_output_dir));
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
- if (!DirAccess::exists(p_output_dir)) {
- Error err = da->make_dir_recursive(p_output_dir);
+ if (!DirAccess::exists(output_dir)) {
+ Error err = da->make_dir_recursive(output_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
- DotNetSolution solution(API_SOLUTION_NAME);
+ Error proj_err;
- if (!solution.set_path(p_output_dir))
- return ERR_FILE_NOT_FOUND;
+ // Generate GodotSharp source files
- Error proj_err;
+ String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME);
+ Vector<String> core_compile_items;
- proj_err = generate_cs_core_project(p_output_dir, solution, p_verbose_output);
+ proj_err = generate_cs_core_project(core_proj_dir, core_compile_items);
if (proj_err != OK) {
ERR_PRINT("Generation of the Core API C# project failed");
return proj_err;
}
- proj_err = generate_cs_editor_project(p_output_dir, solution, p_verbose_output);
+ // Generate GodotSharpEditor source files
+
+ String editor_proj_dir = output_dir.plus_file(EDITOR_API_ASSEMBLY_NAME);
+ Vector<String> editor_compile_items;
+
+ proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items);
if (proj_err != OK) {
ERR_PRINT("Generation of the Editor API C# project failed");
return proj_err;
}
- Error sln_error = solution.save();
- if (sln_error != OK) {
- ERR_PRINT("Failed to save API solution");
- return sln_error;
+ // Generate solution
+
+ if (!CSharpProject::generate_api_solution(output_dir,
+ core_proj_dir, core_compile_items, editor_proj_dir, editor_compile_items)) {
+ return ERR_CANT_CREATE;
}
+ _log("The solution for the Godot API was generated successfully\n");
+
return OK;
}
@@ -1169,65 +1134,64 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls;
- if (verbose_output)
- OS::get_singleton()->print("Generating %s.cs...\n", itype.proxy_name.utf8().get_data());
+ _log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data());
String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types
- List<String> output;
+ StringBuilder output;
- output.push_back("using System;\n"); // IntPtr
- output.push_back("using System.Diagnostics;\n"); // DebuggerBrowsable
+ output.append("using System;\n"); // IntPtr
+ output.append("using System.Diagnostics;\n"); // DebuggerBrowsable
- output.push_back("\n"
- "#pragma warning disable CS1591 // Disable warning: "
- "'Missing XML comment for publicly visible type or member'\n"
- "#pragma warning disable CS1573 // Disable warning: "
- "'Parameter has no matching param tag in the XML comment'\n");
+ output.append("\n"
+ "#pragma warning disable CS1591 // Disable warning: "
+ "'Missing XML comment for publicly visible type or member'\n"
+ "#pragma warning disable CS1573 // Disable warning: "
+ "'Parameter has no matching param tag in the XML comment'\n");
- output.push_back("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
+ output.append("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
const DocData::ClassDoc *class_doc = itype.class_doc;
if (class_doc && class_doc->description.size()) {
String xml_summary = bbcode_to_xml(fix_doc_description(class_doc->description), &itype);
- Vector<String> summary_lines = xml_summary.split("\n");
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
- output.push_back(INDENT1 "/// <summary>\n");
+ output.append(INDENT1 "/// <summary>\n");
for (int i = 0; i < summary_lines.size(); i++) {
- output.push_back(INDENT1 "/// ");
- output.push_back(summary_lines[i]);
- output.push_back("\n");
+ output.append(INDENT1 "/// ");
+ output.append(summary_lines[i]);
+ output.append("\n");
}
- output.push_back(INDENT1 "/// </summary>\n");
+ output.append(INDENT1 "/// </summary>\n");
}
}
- output.push_back(INDENT1 "public ");
+ output.append(INDENT1 "public ");
if (itype.is_singleton) {
- output.push_back("static partial class ");
+ output.append("static partial class ");
} else {
- output.push_back(itype.is_instantiable ? "partial class " : "abstract partial class ");
+ output.append(itype.is_instantiable ? "partial class " : "abstract partial class ");
}
- output.push_back(itype.proxy_name);
+ output.append(itype.proxy_name);
if (itype.is_singleton) {
- output.push_back("\n");
+ output.append("\n");
} else if (is_derived_type) {
if (obj_types.has(itype.base_name)) {
- output.push_back(" : ");
- output.push_back(obj_types[itype.base_name].proxy_name);
- output.push_back("\n");
+ output.append(" : ");
+ output.append(obj_types[itype.base_name].proxy_name);
+ output.append("\n");
} else {
ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class " + itype.name);
return ERR_INVALID_DATA;
}
}
- output.push_back(INDENT1 "{");
+ output.append(INDENT1 "{");
if (class_doc) {
@@ -1238,30 +1202,30 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
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.split("\n");
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
- output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ output.append(MEMBER_BEGIN "/// <summary>\n");
for (int i = 0; i < summary_lines.size(); i++) {
- output.push_back(INDENT2 "/// ");
- output.push_back(summary_lines[i]);
- output.push_back("\n");
+ output.append(INDENT2 "/// ");
+ output.append(summary_lines[i]);
+ output.append("\n");
}
- output.push_back(INDENT2 "/// </summary>");
+ output.append(INDENT2 "/// </summary>");
}
}
- output.push_back(MEMBER_BEGIN "public const int ");
- output.push_back(iconstant.proxy_name);
- output.push_back(" = ");
- output.push_back(itos(iconstant.value));
- output.push_back(";");
+ output.append(MEMBER_BEGIN "public const int ");
+ output.append(iconstant.proxy_name);
+ output.append(" = ");
+ output.append(itos(iconstant.value));
+ output.append(";");
}
if (itype.constants.size())
- output.push_back("\n");
+ output.append("\n");
// Add enums
@@ -1270,38 +1234,38 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
ERR_FAIL_COND_V(ienum.constants.empty(), ERR_BUG);
- output.push_back(MEMBER_BEGIN "public enum ");
- output.push_back(ienum.cname.operator String());
- output.push_back(MEMBER_BEGIN OPEN_BLOCK);
+ output.append(MEMBER_BEGIN "public enum ");
+ output.append(ienum.cname.operator String());
+ output.append(MEMBER_BEGIN OPEN_BLOCK);
for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) {
const ConstantInterface &iconstant = F->get();
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
- Vector<String> summary_lines = xml_summary.split("\n");
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
- output.push_back(INDENT3 "/// <summary>\n");
+ output.append(INDENT3 "/// <summary>\n");
for (int i = 0; i < summary_lines.size(); i++) {
- output.push_back(INDENT3 "/// ");
- output.push_back(summary_lines[i]);
- output.push_back("\n");
+ output.append(INDENT3 "/// ");
+ output.append(summary_lines[i]);
+ output.append("\n");
}
- output.push_back(INDENT3 "/// </summary>\n");
+ output.append(INDENT3 "/// </summary>\n");
}
}
- output.push_back(INDENT3);
- output.push_back(iconstant.proxy_name);
- output.push_back(" = ");
- output.push_back(itos(iconstant.value));
- output.push_back(F != ienum.constants.back() ? ",\n" : "\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.push_back(INDENT2 CLOSE_BLOCK);
+ output.append(INDENT2 CLOSE_BLOCK);
}
// Add properties
@@ -1322,53 +1286,54 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
if (itype.is_singleton) {
// Add the type name and the singleton pointer as static fields
- output.push_back(MEMBER_BEGIN "private static Godot.Object singleton;\n");
- output.push_back(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3
- "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5
- "singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4
- "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n");
-
- output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
- output.push_back(itype.name);
- output.push_back("\";\n");
-
- output.push_back(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = ");
- output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS);
- output.push_back("." ICALL_PREFIX);
- output.push_back(itype.name);
- output.push_back(SINGLETON_ICALL_SUFFIX "();\n");
+ output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n");
+ output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3
+ "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5
+ "singleton = Engine.GetSingleton(typeof(");
+ output.append(itype.proxy_name);
+ output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n");
+
+ output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
+ output.append(itype.name);
+ output.append("\";\n");
+
+ output.append(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = ");
+ output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS);
+ output.append("." ICALL_PREFIX);
+ output.append(itype.name);
+ output.append(SINGLETON_ICALL_SUFFIX "();\n");
} else if (is_derived_type) {
// Add member fields
- output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
- output.push_back(itype.name);
- output.push_back("\";\n");
+ output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
+ output.append(itype.name);
+ output.append("\";\n");
// Add default constructor
if (itype.is_instantiable) {
- output.push_back(MEMBER_BEGIN "public ");
- output.push_back(itype.proxy_name);
- output.push_back("() : this(");
- output.push_back(itype.memory_own ? "true" : "false");
+ output.append(MEMBER_BEGIN "public ");
+ output.append(itype.proxy_name);
+ output.append("() : this(");
+ output.append(itype.memory_own ? "true" : "false");
// The default constructor may also be called by the engine when instancing existing native objects
// The engine will initialize the pointer field of the managed side before calling the constructor
// This is why we only allocate a new native object from the constructor if the pointer field is not set
- output.push_back(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = ");
- output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS);
- output.push_back("." + ctor_method);
- output.push_back("(this);\n" CLOSE_BLOCK_L2);
+ output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = ");
+ output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS);
+ output.append("." + ctor_method);
+ output.append("(this);\n" CLOSE_BLOCK_L2);
} else {
// Hide the constructor
- output.push_back(MEMBER_BEGIN "internal ");
- output.push_back(itype.proxy_name);
- output.push_back("() {}\n");
+ output.append(MEMBER_BEGIN "internal ");
+ output.append(itype.proxy_name);
+ output.append("() {}\n");
}
// Add.. em.. trick constructor. Sort of.
- output.push_back(MEMBER_BEGIN "internal ");
- output.push_back(itype.proxy_name);
- output.push_back("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n");
+ output.append(MEMBER_BEGIN "internal ");
+ output.append(itype.proxy_name);
+ output.append("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n");
}
int method_bind_count = 0;
@@ -1395,17 +1360,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
custom_icalls.push_back(ctor_icall);
}
- output.push_back(INDENT1 CLOSE_BLOCK /* class */
+ output.append(INDENT1 CLOSE_BLOCK /* class */
CLOSE_BLOCK /* namespace */);
- output.push_back("\n"
- "#pragma warning restore CS1591\n"
- "#pragma warning restore CS1573\n");
+ output.append("\n"
+ "#pragma warning restore CS1591\n"
+ "#pragma warning restore CS1573\n");
return _save_file(p_output_file, output);
}
-Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, List<String> &p_output) {
+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);
@@ -1452,72 +1417,94 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
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.split("\n");
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
- p_output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
for (int i = 0; i < summary_lines.size(); i++) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(summary_lines[i]);
- p_output.push_back("\n");
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- p_output.push_back(INDENT2 "/// </summary>");
+ p_output.append(INDENT2 "/// </summary>");
}
}
- p_output.push_back(MEMBER_BEGIN "public ");
+ p_output.append(MEMBER_BEGIN "public ");
if (p_itype.is_singleton)
- p_output.push_back("static ");
+ p_output.append("static ");
- p_output.push_back(prop_itype->cs_type);
- p_output.push_back(" ");
- p_output.push_back(p_iprop.proxy_name);
- p_output.push_back("\n" INDENT2 OPEN_BLOCK);
+ p_output.append(prop_itype->cs_type);
+ p_output.append(" ");
+ p_output.append(p_iprop.proxy_name);
+ p_output.append("\n" INDENT2 OPEN_BLOCK);
if (getter) {
- p_output.push_back(INDENT3 "get\n" OPEN_BLOCK_L3);
- p_output.push_back("return ");
- p_output.push_back(getter->proxy_name + "(");
+ p_output.append(INDENT3 "get\n"
+
+ // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that)
+ "#pragma warning disable CS0618 // Disable warning about obsolete method\n"
+
+ OPEN_BLOCK_L3);
+
+ p_output.append("return ");
+ p_output.append(getter->proxy_name + "(");
if (p_iprop.index != -1) {
const ArgumentInterface &idx_arg = getter->arguments.front()->get();
if (idx_arg.type.cname != name_cache.type_int) {
// Assume the index parameter is an enum
const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);
CRASH_COND(idx_arg_type == NULL);
- p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index));
+ p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index));
} else {
- p_output.push_back(itos(p_iprop.index));
+ p_output.append(itos(p_iprop.index));
}
}
- p_output.push_back(");\n" CLOSE_BLOCK_L3);
+ p_output.append(");\n"
+
+ CLOSE_BLOCK_L3
+
+ // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that)
+ "#pragma warning restore CS0618\n");
}
if (setter) {
- p_output.push_back(INDENT3 "set\n" OPEN_BLOCK_L3);
- p_output.push_back(setter->proxy_name + "(");
+ p_output.append(INDENT3 "set\n"
+
+ // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that)
+ "#pragma warning disable CS0618 // Disable warning about obsolete method\n"
+
+ OPEN_BLOCK_L3);
+
+ p_output.append(setter->proxy_name + "(");
if (p_iprop.index != -1) {
const ArgumentInterface &idx_arg = setter->arguments.front()->get();
if (idx_arg.type.cname != name_cache.type_int) {
// Assume the index parameter is an enum
const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);
CRASH_COND(idx_arg_type == NULL);
- p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", ");
+ p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", ");
} else {
- p_output.push_back(itos(p_iprop.index) + ", ");
+ p_output.append(itos(p_iprop.index) + ", ");
}
}
- p_output.push_back("value);\n" CLOSE_BLOCK_L3);
+ p_output.append("value);\n"
+
+ CLOSE_BLOCK_L3
+
+ // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that)
+ "#pragma warning restore CS0618\n");
}
- p_output.push_back(CLOSE_BLOCK_L2);
+ p_output.append(CLOSE_BLOCK_L2);
return OK;
}
-Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, List<String> &p_output) {
+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);
@@ -1529,7 +1516,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
String icall_params = method_bind_field + ", ";
icall_params += sformat(p_itype.cs_in, "this");
- List<String> default_args_doc;
+ StringBuilder default_args_doc;
// Retrieve information from the arguments
for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) {
@@ -1598,7 +1585,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
// Apparently the name attribute must not include the @
String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name;
- default_args_doc.push_back(INDENT2 "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is " + def_arg + "</param>\n");
+ default_args_doc.append(INDENT2 "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is " + def_arg + "</param>\n");
} else {
icall_params += arg_type->cs_in.empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name);
}
@@ -1607,61 +1594,67 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
// Generate method
{
if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
- p_output.push_back(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr ");
- p_output.push_back(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \"");
- p_output.push_back(p_imethod.name);
- p_output.push_back("\");\n");
+ p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr ");
+ p_output.append(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \"");
+ p_output.append(p_imethod.name);
+ p_output.append("\");\n");
}
if (p_imethod.method_doc && p_imethod.method_doc->description.size()) {
String xml_summary = bbcode_to_xml(fix_doc_description(p_imethod.method_doc->description), &p_itype);
- Vector<String> summary_lines = xml_summary.split("\n");
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- if (summary_lines.size() || default_args_doc.size()) {
- p_output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ if (summary_lines.size() || default_args_doc.get_string_length()) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
for (int i = 0; i < summary_lines.size(); i++) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(summary_lines[i]);
- p_output.push_back("\n");
- }
-
- for (List<String>::Element *E = default_args_doc.front(); E; E = E->next()) {
- p_output.push_back(E->get());
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- p_output.push_back(INDENT2 "/// </summary>");
+ p_output.append(default_args_doc.as_string());
+ p_output.append(INDENT2 "/// </summary>");
}
}
if (!p_imethod.is_internal) {
- p_output.push_back(MEMBER_BEGIN "[GodotMethod(\"");
- p_output.push_back(p_imethod.name);
- p_output.push_back("\")]");
+ p_output.append(MEMBER_BEGIN "[GodotMethod(\"");
+ p_output.append(p_imethod.name);
+ p_output.append("\")]");
}
- p_output.push_back(MEMBER_BEGIN);
- p_output.push_back(p_imethod.is_internal ? "internal " : "public ");
+ if (p_imethod.is_deprecated) {
+ if (p_imethod.deprecation_message.empty())
+ WARN_PRINTS("An empty deprecation message is discouraged. Method: " + p_imethod.proxy_name);
+
+ p_output.append(MEMBER_BEGIN "[Obsolete(\"");
+ p_output.append(p_imethod.deprecation_message);
+ p_output.append("\")]");
+ }
+
+ p_output.append(MEMBER_BEGIN);
+ p_output.append(p_imethod.is_internal ? "internal " : "public ");
if (p_itype.is_singleton) {
- p_output.push_back("static ");
+ p_output.append("static ");
} else if (p_imethod.is_virtual) {
- p_output.push_back("virtual ");
+ p_output.append("virtual ");
}
- p_output.push_back(return_type->cs_type + " ");
- p_output.push_back(p_imethod.proxy_name + "(");
- p_output.push_back(arguments_sig + ")\n" OPEN_BLOCK_L2);
+ p_output.append(return_type->cs_type + " ");
+ p_output.append(p_imethod.proxy_name + "(");
+ p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L2);
if (p_imethod.is_virtual) {
// Godot virtual method must be overridden, therefore we return a default value by default.
if (return_type->cname == name_cache.type_void) {
- p_output.push_back("return;\n" CLOSE_BLOCK_L2);
+ p_output.append("return;\n" CLOSE_BLOCK_L2);
} else {
- p_output.push_back("return default(");
- p_output.push_back(return_type->cs_type);
- p_output.push_back(");\n" CLOSE_BLOCK_L2);
+ p_output.append("return default(");
+ p_output.append(return_type->cs_type);
+ p_output.append(");\n" CLOSE_BLOCK_L2);
}
return OK; // Won't increment method bind count
@@ -1670,16 +1663,16 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
if (p_imethod.requires_object_call) {
// Fallback to Godot's object.Call(string, params)
- p_output.push_back(CS_METHOD_CALL "(\"");
- p_output.push_back(p_imethod.name);
- p_output.push_back("\"");
+ p_output.append(CS_METHOD_CALL "(\"");
+ p_output.append(p_imethod.name);
+ p_output.append("\"");
for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) {
- p_output.push_back(", ");
- p_output.push_back(F->get().name);
+ p_output.append(", ");
+ p_output.append(F->get().name);
}
- p_output.push_back(");\n" CLOSE_BLOCK_L2);
+ p_output.append(");\n" CLOSE_BLOCK_L2);
return OK; // Won't increment method bind count
}
@@ -1693,37 +1686,36 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
im_call += "." + im_icall->name + "(" + icall_params + ")";
if (p_imethod.arguments.size())
- p_output.push_back(cs_in_statements);
+ p_output.append(cs_in_statements);
if (return_type->cname == name_cache.type_void) {
- p_output.push_back(im_call + ";\n");
+ p_output.append(im_call + ";\n");
} else if (return_type->cs_out.empty()) {
- p_output.push_back("return " + im_call + ";\n");
+ p_output.append("return " + im_call + ";\n");
} else {
- p_output.push_back(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out));
- p_output.push_back("\n");
+ p_output.append(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out));
+ p_output.append("\n");
}
- p_output.push_back(CLOSE_BLOCK_L2);
+ p_output.append(CLOSE_BLOCK_L2);
}
p_method_bind_count++;
+
return OK;
}
Error BindingsGenerator::generate_glue(const String &p_output_dir) {
- verbose_output = true;
-
bool dir_exists = DirAccess::exists(p_output_dir);
ERR_EXPLAIN("The output directory does not exist.");
ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH);
- List<String> output;
+ StringBuilder output;
- output.push_back("/* THIS FILE IS GENERATED DO NOT EDIT */\n");
- output.push_back("#include \"" GLUE_HEADER_FILE "\"\n");
- output.push_back("\n#ifdef MONO_GLUE_ENABLED\n");
+ output.append("/* THIS FILE IS GENERATED DO NOT EDIT */\n");
+ output.append("#include \"" GLUE_HEADER_FILE "\"\n");
+ output.append("\n#ifdef MONO_GLUE_ENABLED\n");
generated_icall_funcs.clear();
@@ -1763,11 +1755,11 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
if (!find_icall_by_name(singleton_icall.name, custom_icalls))
custom_icalls.push_back(singleton_icall);
- output.push_back("Object* ");
- output.push_back(singleton_icall_name);
- output.push_back("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\"");
- output.push_back(itype.proxy_name);
- output.push_back("\");\n" CLOSE_BLOCK "\n");
+ output.append("Object* ");
+ output.append(singleton_icall_name);
+ output.append("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\"");
+ output.append(itype.proxy_name);
+ output.append("\");\n" CLOSE_BLOCK "\n");
}
if (is_derived_type && itype.is_instantiable) {
@@ -1776,43 +1768,43 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
if (!find_icall_by_name(ctor_icall.name, custom_icalls))
custom_icalls.push_back(ctor_icall);
- output.push_back("Object* ");
- output.push_back(ctor_method);
- output.push_back("(MonoObject* obj) " OPEN_BLOCK
- "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \"");
- output.push_back(itype.name);
- output.push_back("\");\n"
- "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n"
- "\treturn instance;\n" CLOSE_BLOCK "\n");
+ output.append("Object* ");
+ output.append(ctor_method);
+ output.append("(MonoObject* obj) " OPEN_BLOCK
+ "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \"");
+ output.append(itype.name);
+ output.append("\");\n"
+ "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n"
+ "\treturn instance;\n" CLOSE_BLOCK "\n");
}
}
- output.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK "\n");
+ output.append("namespace GodotSharpBindings\n" OPEN_BLOCK "\n");
- output.push_back("uint64_t get_core_api_hash() { return ");
- output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n");
+ output.append("uint64_t get_core_api_hash() { return ");
+ output.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n");
- output.push_back("#ifdef TOOLS_ENABLED\n"
- "uint64_t get_editor_api_hash() { return ");
- output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n");
- output.push_back("#endif // TOOLS_ENABLED\n");
+ output.append("#ifdef TOOLS_ENABLED\n"
+ "uint64_t get_editor_api_hash() { return ");
+ output.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n");
+ output.append("#endif // TOOLS_ENABLED\n");
- output.push_back("uint32_t get_bindings_version() { return ");
- output.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n");
+ output.append("uint32_t get_bindings_version() { return ");
+ output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n");
- output.push_back("\nvoid register_generated_icalls() " OPEN_BLOCK);
- output.push_back("\tgodot_register_glue_header_icalls();\n");
+ output.append("\nvoid register_generated_icalls() " OPEN_BLOCK);
+ output.append("\tgodot_register_glue_header_icalls();\n");
-#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \
- { \
- output.push_back("\tmono_add_internal_call("); \
- output.push_back("\"" BINDINGS_NAMESPACE "."); \
- output.push_back(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \
- output.push_back("::"); \
- output.push_back(m_icall.name); \
- output.push_back("\", (void*)"); \
- output.push_back(m_icall.name); \
- output.push_back(");\n"); \
+#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \
+ { \
+ output.append("\tmono_add_internal_call("); \
+ output.append("\"" BINDINGS_NAMESPACE "."); \
+ output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \
+ output.append("::"); \
+ output.append(m_icall.name); \
+ output.append("\", (void*)"); \
+ output.append(m_icall.name); \
+ output.append(");\n"); \
}
bool tools_sequence = false;
@@ -1821,11 +1813,11 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
if (tools_sequence) {
if (!E->get().editor_only) {
tools_sequence = false;
- output.push_back("#endif\n");
+ output.append("#endif\n");
}
} else {
if (E->get().editor_only) {
- output.push_back("#ifdef TOOLS_ENABLED\n");
+ output.append("#ifdef TOOLS_ENABLED\n");
tools_sequence = true;
}
}
@@ -1835,23 +1827,23 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
if (tools_sequence) {
tools_sequence = false;
- output.push_back("#endif\n");
+ output.append("#endif\n");
}
- output.push_back("#ifdef TOOLS_ENABLED\n");
+ output.append("#ifdef TOOLS_ENABLED\n");
for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next())
ADD_INTERNAL_CALL_REGISTRATION(E->get());
- output.push_back("#endif // TOOLS_ENABLED\n");
+ output.append("#endif // TOOLS_ENABLED\n");
for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) {
if (tools_sequence) {
if (!E->get().editor_only) {
tools_sequence = false;
- output.push_back("#endif\n");
+ output.append("#endif\n");
}
} else {
if (E->get().editor_only) {
- output.push_back("#ifdef TOOLS_ENABLED\n");
+ output.append("#ifdef TOOLS_ENABLED\n");
tools_sequence = true;
}
}
@@ -1861,16 +1853,16 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
if (tools_sequence) {
tools_sequence = false;
- output.push_back("#endif\n");
+ output.append("#endif\n");
}
#undef ADD_INTERNAL_CALL_REGISTRATION
- output.push_back(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n");
+ output.append(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n");
- output.push_back("\n#endif // MONO_GLUE_ENABLED\n");
+ output.append("\n#endif // MONO_GLUE_ENABLED\n");
- Error save_err = _save_file(path_join(p_output_dir, "mono_glue.gen.cpp"), output);
+ Error save_err = _save_file(path::join(p_output_dir, "mono_glue.gen.cpp"), output);
if (save_err != OK)
return save_err;
@@ -1883,23 +1875,20 @@ uint32_t BindingsGenerator::get_version() {
return BINDINGS_GENERATOR_VERSION;
}
-Error BindingsGenerator::_save_file(const String &p_path, const List<String> &p_content) {
+Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) {
FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE);
ERR_EXPLAIN("Cannot open file: " + p_path);
ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE);
- for (const List<String>::Element *E = p_content.front(); E; E = E->next()) {
- file->store_string(E->get());
- }
-
+ file->store_string(p_content.as_string());
file->close();
return OK;
}
-Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, List<String> &p_output) {
+Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, StringBuilder &p_output) {
if (p_imethod.is_virtual)
return OK; // Ignore
@@ -1955,15 +1944,15 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
generated_icall_funcs.push_back(im_icall);
if (im_icall->editor_only)
- p_output.push_back("#ifdef TOOLS_ENABLED\n");
+ p_output.append("#ifdef TOOLS_ENABLED\n");
// Generate icall function
- p_output.push_back(ret_void ? "void " : return_type->c_type_out + " ");
- p_output.push_back(icall_method);
- p_output.push_back("(");
- p_output.push_back(c_func_sig);
- p_output.push_back(") " OPEN_BLOCK);
+ p_output.append(ret_void ? "void " : return_type->c_type_out + " ");
+ p_output.append(icall_method);
+ p_output.append("(");
+ p_output.append(c_func_sig);
+ p_output.append(") " OPEN_BLOCK);
String fail_ret = ret_void ? "" : ", " + (return_type->c_type_out.ends_with("*") ? "NULL" : return_type->c_type_out + "()");
@@ -1977,7 +1966,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
// the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr,
// it could be deleted too early. This is the case with GDScript.new() which returns OBJECT.
// Alternatively, we could just return Variant, but that would result in a worse API.
- p_output.push_back("\tVariant " C_LOCAL_VARARG_RET ";\n");
+ p_output.append("\tVariant " C_LOCAL_VARARG_RET ";\n");
}
if (return_type->is_object_type) {
@@ -1987,83 +1976,82 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
ptrcall_return_type = return_type->c_type;
}
- p_output.push_back("\t" + ptrcall_return_type);
- p_output.push_back(" " C_LOCAL_RET);
- p_output.push_back(initialization + ";\n");
- p_output.push_back("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE);
- p_output.push_back(fail_ret);
- p_output.push_back(");\n");
+ p_output.append("\t" + ptrcall_return_type);
+ p_output.append(" " C_LOCAL_RET);
+ p_output.append(initialization + ";\n");
+ p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE);
+ p_output.append(fail_ret);
+ p_output.append(");\n");
} else {
- p_output.push_back("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n");
+ p_output.append("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n");
}
if (p_imethod.arguments.size()) {
if (p_imethod.is_vararg) {
- String err_fail_macro = ret_void ? "ERR_FAIL_COND" : "ERR_FAIL_COND_V";
String vararg_arg = "arg" + argc_str;
String real_argc_str = itos(p_imethod.arguments.size() - 1); // Arguments count without vararg
- p_output.push_back("\tint vararg_length = mono_array_length(");
- p_output.push_back(vararg_arg);
- p_output.push_back(");\n\tint total_length = ");
- p_output.push_back(real_argc_str);
- p_output.push_back(" + vararg_length;\n"
- "\tArgumentsVector<Variant> varargs(vararg_length);\n"
- "\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n");
- p_output.push_back(c_in_statements);
- p_output.push_back("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK
- "\t\tMonoObject* elem = mono_array_get(");
- p_output.push_back(vararg_arg);
- p_output.push_back(", MonoObject*, i);\n"
- "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n"
- "\t\t" C_LOCAL_PTRCALL_ARGS ".set(");
- p_output.push_back(real_argc_str);
- p_output.push_back(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK);
+ p_output.append("\tint vararg_length = mono_array_length(");
+ p_output.append(vararg_arg);
+ p_output.append(");\n\tint total_length = ");
+ p_output.append(real_argc_str);
+ p_output.append(" + vararg_length;\n"
+ "\tArgumentsVector<Variant> varargs(vararg_length);\n"
+ "\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n");
+ p_output.append(c_in_statements);
+ p_output.append("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK
+ "\t\tMonoObject* elem = mono_array_get(");
+ p_output.append(vararg_arg);
+ p_output.append(", MonoObject*, i);\n"
+ "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n"
+ "\t\t" C_LOCAL_PTRCALL_ARGS ".set(");
+ p_output.append(real_argc_str);
+ p_output.append(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK);
} else {
- p_output.push_back(c_in_statements);
- p_output.push_back("\tconst void* " C_LOCAL_PTRCALL_ARGS "[");
- p_output.push_back(argc_str + "] = { ");
- p_output.push_back(c_args_var_content + " };\n");
+ p_output.append(c_in_statements);
+ p_output.append("\tconst void* " C_LOCAL_PTRCALL_ARGS "[");
+ p_output.append(argc_str + "] = { ");
+ p_output.append(c_args_var_content + " };\n");
}
}
if (p_imethod.is_vararg) {
- p_output.push_back("\tVariant::CallError vcall_error;\n\t");
+ p_output.append("\tVariant::CallError vcall_error;\n\t");
if (!ret_void) {
// See the comment on the C_LOCAL_VARARG_RET declaration
if (return_type->cname != name_cache.type_Variant) {
- p_output.push_back(C_LOCAL_VARARG_RET " = ");
+ p_output.append(C_LOCAL_VARARG_RET " = ");
} else {
- p_output.push_back(C_LOCAL_RET " = ");
+ p_output.append(C_LOCAL_RET " = ");
}
}
- p_output.push_back(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", ");
- p_output.push_back(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL");
- p_output.push_back(", total_length, vcall_error);\n");
+ p_output.append(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", ");
+ p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL");
+ p_output.append(", total_length, vcall_error);\n");
// See the comment on the C_LOCAL_VARARG_RET declaration
if (return_type->cname != name_cache.type_Variant) {
- p_output.push_back("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n");
+ p_output.append("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n");
}
} else {
- p_output.push_back("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", ");
- p_output.push_back(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, ");
- p_output.push_back(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n");
+ p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", ");
+ p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, ");
+ p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n");
}
if (!ret_void) {
if (return_type->c_out.empty())
- p_output.push_back("\treturn " C_LOCAL_RET ";\n");
+ p_output.append("\treturn " C_LOCAL_RET ";\n");
else
- p_output.push_back(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name));
+ p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name));
}
- p_output.push_back(CLOSE_BLOCK "\n");
+ p_output.append(CLOSE_BLOCK "\n");
if (im_icall->editor_only)
- p_output.push_back("#endif // TOOLS_ENABLED\n");
+ p_output.append("#endif // TOOLS_ENABLED\n");
}
return OK;
@@ -2116,6 +2104,58 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol
return &placeholder_types.insert(placeholder.cname, placeholder)->get();
}
+StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {
+
+ switch (p_meta) {
+ case GodotTypeInfo::METADATA_INT_IS_INT8:
+ return "sbyte";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_INT16:
+ return "short";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_INT32:
+ return "int";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_INT64:
+ return "long";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_UINT8:
+ return "byte";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_UINT16:
+ return "ushort";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_UINT32:
+ return "uint";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_UINT64:
+ return "ulong";
+ break;
+ default:
+ // Assume INT32
+ return "int";
+ }
+}
+
+StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {
+
+ switch (p_meta) {
+ case GodotTypeInfo::METADATA_REAL_IS_FLOAT:
+ return "float";
+ break;
+ case GodotTypeInfo::METADATA_REAL_IS_DOUBLE:
+ return "double";
+ break;
+ default:
+ // Assume real_t (float or double depending of REAL_T_IS_DOUBLE)
+#ifdef REAL_T_IS_DOUBLE
+ return "double";
+#else
+ return "float";
+#endif
+ }
+}
+
void BindingsGenerator::_populate_object_type_interfaces() {
obj_types.clear();
@@ -2135,15 +2175,13 @@ void BindingsGenerator::_populate_object_type_interfaces() {
}
if (!ClassDB::is_class_exposed(type_cname)) {
- if (verbose_output)
- WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not exposed");
+ _log("Ignoring type `%s` because it's not exposed\n", String(type_cname).utf8().get_data());
class_list.pop_front();
continue;
}
if (!ClassDB::is_class_enabled(type_cname)) {
- if (verbose_output)
- WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not enabled");
+ _log("Ignoring type `%s` because it's not enabled\n", String(type_cname).utf8().get_data());
class_list.pop_front();
continue;
}
@@ -2154,7 +2192,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
itype.base_name = ClassDB::get_parent_class(type_cname);
itype.is_singleton = Engine::get_singleton()->has_singleton(itype.proxy_name);
- itype.is_instantiable = ClassDB::can_instance(type_cname) && !itype.is_singleton;
+ itype.is_instantiable = class_info->creation_func && !itype.is_singleton;
itype.is_reference = ClassDB::is_parent_class(type_cname, name_cache.type_Reference);
itype.memory_own = itype.is_reference;
@@ -2171,10 +2209,12 @@ void BindingsGenerator::_populate_object_type_interfaces() {
itype.im_type_in = "IntPtr";
itype.im_type_out = itype.proxy_name;
+ // Populate properties
+
List<PropertyInfo> property_list;
ClassDB::get_property_list(type_cname, &property_list, true);
- // Populate properties
+ Map<StringName, StringName> accessor_methods;
for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) {
const PropertyInfo &property = E->get();
@@ -2187,18 +2227,21 @@ void BindingsGenerator::_populate_object_type_interfaces() {
iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);
iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname);
+ if (iprop.setter != StringName())
+ accessor_methods[iprop.setter] = iprop.cname;
+ 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(!valid);
iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
- // Prevent property and enclosing type from sharing the same name
+ // Prevent the property and its enclosing type from sharing the same name
if (iprop.proxy_name == itype.proxy_name) {
- if (verbose_output) {
- WARN_PRINTS("Name of property `" + iprop.proxy_name + "` is ambiguous with the name of its class `" +
- itype.proxy_name + "`. Renaming property to `" + iprop.proxy_name + "_`");
- }
+ _log("Name of property `%s` is ambiguous with the name of its enclosing class `%s`. Renaming property to `%s_`\n",
+ iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data());
iprop.proxy_name += "_";
}
@@ -2236,9 +2279,14 @@ void BindingsGenerator::_populate_object_type_interfaces() {
if (method_info.name.empty())
continue;
+ String cname = method_info.name;
+
+ if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname))
+ continue;
+
MethodInterface imethod;
imethod.name = method_info.name;
- imethod.cname = imethod.name;
+ imethod.cname = cname;
if (method_info.flags & METHOD_FLAG_VIRTUAL)
imethod.is_virtual = true;
@@ -2265,20 +2313,26 @@ void BindingsGenerator::_populate_object_type_interfaces() {
// which could actually will return something different.
// Let's put this to notify us if that ever happens.
if (itype.cname != name_cache.type_Object || imethod.name != "free") {
- if (verbose_output) {
- WARN_PRINTS("Notification: New unexpected virtual non-overridable method found.\n"
- "We only expected Object.free, but found " +
- itype.name + "." + imethod.name);
- }
+ ERR_PRINTS("Notification: New unexpected virtual non-overridable method found.\n"
+ "We only expected Object.free, but found " +
+ itype.name + "." + imethod.name);
}
} else {
- ERR_PRINTS("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name);
+ ERR_EXPLAIN("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name);
+ ERR_FAIL();
}
} else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
imethod.return_type.cname = return_info.class_name;
imethod.return_type.is_enum = true;
} else if (return_info.class_name != StringName()) {
imethod.return_type.cname = return_info.class_name;
+ if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) {
+ /* clang-format off */
+ ERR_PRINTS("Return type is reference but hint is not " _STR(PROPERTY_HINT_RESOURCE_TYPE) "."
+ " Are you returning a reference type by pointer? Method: " + itype.name + "." + imethod.name);
+ /* clang-format on */
+ ERR_FAIL();
+ }
} 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) {
@@ -2286,7 +2340,13 @@ void BindingsGenerator::_populate_object_type_interfaces() {
} else if (return_info.type == Variant::NIL) {
imethod.return_type.cname = name_cache.type_void;
} else {
- imethod.return_type.cname = Variant::get_type_name(return_info.type);
+ if (return_info.type == Variant::INT) {
+ imethod.return_type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE);
+ } else if (return_info.type == Variant::REAL) {
+ imethod.return_type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE);
+ } else {
+ imethod.return_type.cname = Variant::get_type_name(return_info.type);
+ }
}
for (int i = 0; i < argc; i++) {
@@ -2305,7 +2365,13 @@ void BindingsGenerator::_populate_object_type_interfaces() {
} else if (arginfo.type == Variant::NIL) {
iarg.type.cname = name_cache.type_Variant;
} else {
- iarg.type.cname = Variant::get_type_name(arginfo.type);
+ if (arginfo.type == Variant::INT) {
+ iarg.type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE);
+ } else if (arginfo.type == Variant::REAL) {
+ iarg.type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE);
+ } else {
+ iarg.type.cname = Variant::get_type_name(arginfo.type);
+ }
}
iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));
@@ -2326,16 +2392,24 @@ void BindingsGenerator::_populate_object_type_interfaces() {
imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name));
- // Prevent naming the property and its enclosing type from sharing the same name
+ // Prevent the method and its enclosing type from sharing the same name
if (imethod.proxy_name == itype.proxy_name) {
- if (verbose_output) {
- WARN_PRINTS("Name of method `" + imethod.proxy_name + "` is ambiguous with the name of its class `" +
- itype.proxy_name + "`. Renaming method to `" + imethod.proxy_name + "_`");
- }
+ _log("Name of method `%s` is ambiguous with the name of its enclosing class `%s`. Renaming method to `%s_`\n",
+ imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data());
imethod.proxy_name += "_";
}
+ Map<StringName, StringName>::Element *accessor = accessor_methods.find(imethod.cname);
+ if (accessor) {
+ const PropertyInterface *accessor_property = itype.find_property_by_name(accessor->value());
+
+ // We only deprecate an accessor method if it's in the same class as the property. It's easier this way, but also
+ // we don't know if an accessor method in a different class could have other purposes, so better leave those untouched.
+ imethod.is_deprecated = true;
+ imethod.deprecation_message = imethod.proxy_name + " is deprecated. Use the " + accessor_property->proxy_name + " property instead.";
+ }
+
if (itype.class_doc) {
for (int i = 0; i < itype.class_doc->methods.size(); i++) {
if (itype.class_doc->methods[i].name == imethod.name) {
@@ -2362,8 +2436,8 @@ void BindingsGenerator::_populate_object_type_interfaces() {
// Populate enums and constants
- List<String> constant_list;
- ClassDB::get_integer_constant_list(type_cname, &constant_list, true);
+ List<String> constants;
+ ClassDB::get_integer_constant_list(type_cname, &constants, true);
const HashMap<StringName, List<StringName> > &enum_map = class_info->enum_map;
const StringName *k = NULL;
@@ -2378,13 +2452,13 @@ void BindingsGenerator::_populate_object_type_interfaces() {
enum_proxy_cname = StringName(enum_proxy_name);
}
EnumInterface ienum(enum_proxy_cname);
- const List<StringName> &constants = enum_map.get(*k);
- for (const List<StringName>::Element *E = constants.front(); E; E = E->next()) {
+ const List<StringName> &enum_constants = enum_map.get(*k);
+ for (const List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) {
const StringName &constant_cname = E->get();
String constant_name = constant_cname.operator String();
int *value = class_info->constant_map.getptr(constant_cname);
ERR_FAIL_NULL(value);
- constant_list.erase(constant_name);
+ constants.erase(constant_name);
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
@@ -2416,7 +2490,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
enum_types.insert(enum_itype.cname, enum_itype);
}
- for (const List<String>::Element *E = constant_list.front(); E; E = E->next()) {
+ for (const List<String>::Element *E = constants.front(); E; E = E->next()) {
const String &constant_name = E->get();
int *value = class_info->constant_map.getptr(StringName(E->get()));
ERR_FAIL_NULL(value);
@@ -2448,13 +2522,8 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg
switch (p_val.get_type()) {
case Variant::NIL:
- if (ClassDB::class_exists(r_iarg.type.cname)) {
- // Object type
- r_iarg.default_argument = "null";
- } else {
- // Variant
- r_iarg.default_argument = "null";
- }
+ // Either Object type or Variant
+ r_iarg.default_argument = "null";
break;
// Atomic types
case Variant::BOOL:
@@ -2563,7 +2632,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
// bool
itype = TypeInterface::create_value_type(String("bool"));
-
{
// MonoBoolean <---> bool
itype.c_in = "\t%0 %1_in = (%0)%1;\n";
@@ -2577,45 +2645,73 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.im_type_out = itype.name;
builtin_types.insert(itype.cname, itype);
- // int
- // C interface is the same as that of enums. Remember to apply any
- // changes done here to TypeInterface::postsetup_enum_type as well
- itype = TypeInterface::create_value_type(String("int"));
- itype.c_arg_in = "&%s_in";
+ // Integer types
{
- // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'.
- itype.c_in = "\t%0 %1_in = (%0)%1;\n";
- itype.c_out = "\treturn (%0)%1;\n";
- itype.c_type = "int64_t";
+ // C interface for 'uint32_t' is the same as that of enums. Remember to apply
+ // any of the changes done here to 'TypeInterface::postsetup_enum_type' as well.
+#define INSERT_INT_TYPE(m_name, m_c_type_in_out, m_c_type) \
+ { \
+ itype = TypeInterface::create_value_type(String(m_name)); \
+ { \
+ itype.c_in = "\t%0 %1_in = (%0)%1;\n"; \
+ itype.c_out = "\treturn (%0)%1;\n"; \
+ itype.c_type = #m_c_type; \
+ itype.c_arg_in = "&%s_in"; \
+ } \
+ itype.c_type_in = #m_c_type_in_out; \
+ itype.c_type_out = itype.c_type_in; \
+ itype.im_type_in = itype.name; \
+ itype.im_type_out = itype.name; \
+ builtin_types.insert(itype.cname, itype); \
}
- itype.c_type_in = "int32_t";
- itype.c_type_out = itype.c_type_in;
- itype.im_type_in = itype.name;
- itype.im_type_out = itype.name;
- builtin_types.insert(itype.cname, itype);
- // real_t
- itype = TypeInterface();
- itype.name = "float"; // The name is always "float" in Variant, even with REAL_T_IS_DOUBLE.
- itype.cname = itype.name;
-#ifdef REAL_T_IS_DOUBLE
- itype.proxy_name = "double";
-#else
- itype.proxy_name = "float";
-#endif
+ // The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type'
+
+ INSERT_INT_TYPE("sbyte", int8_t, int64_t);
+ INSERT_INT_TYPE("short", int16_t, int64_t);
+ INSERT_INT_TYPE("int", int32_t, int64_t);
+ INSERT_INT_TYPE("long", int64_t, int64_t);
+ INSERT_INT_TYPE("byte", uint8_t, int64_t);
+ INSERT_INT_TYPE("ushort", uint16_t, int64_t);
+ INSERT_INT_TYPE("uint", uint32_t, int64_t);
+ INSERT_INT_TYPE("ulong", uint64_t, int64_t);
+ }
+
+ // Floating point types
{
- // The expected type for parameters and return value in ptrcall is 'double'.
- itype.c_in = "\t%0 %1_in = (%0)%1;\n";
- itype.c_out = "\treturn (%0)%1;\n";
+ // float
+ itype = TypeInterface();
+ itype.name = "float";
+ itype.cname = itype.name;
+ itype.proxy_name = "float";
+ {
+ // The expected type for 'float' in ptrcall is 'double'
+ itype.c_in = "\t%0 %1_in = (%0)%1;\n";
+ itype.c_out = "\treturn (%0)%1;\n";
+ itype.c_type = "double";
+ itype.c_type_in = "float";
+ itype.c_type_out = "float";
+ itype.c_arg_in = "&%s_in";
+ }
+ itype.cs_type = itype.proxy_name;
+ itype.im_type_in = itype.proxy_name;
+ itype.im_type_out = itype.proxy_name;
+ builtin_types.insert(itype.cname, itype);
+
+ // double
+ itype = TypeInterface();
+ itype.name = "double";
+ itype.cname = itype.name;
+ itype.proxy_name = "double";
itype.c_type = "double";
- itype.c_type_in = "real_t";
- itype.c_type_out = "real_t";
- itype.c_arg_in = "&%s_in";
+ itype.c_type_in = "double";
+ itype.c_type_out = "double";
+ itype.c_arg_in = "&%s";
+ itype.cs_type = itype.proxy_name;
+ itype.im_type_in = itype.proxy_name;
+ itype.im_type_out = itype.proxy_name;
+ builtin_types.insert(itype.cname, itype);
}
- itype.cs_type = itype.proxy_name;
- itype.im_type_in = itype.proxy_name;
- itype.im_type_out = itype.proxy_name;
- builtin_types.insert(itype.cname, itype);
// String
itype = TypeInterface();
@@ -2865,12 +2961,32 @@ void BindingsGenerator::_populate_global_constants() {
}
}
-void BindingsGenerator::initialize() {
+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;
+
+ va_start(list, p_format);
+ OS::get_singleton()->print("%s", str_format(p_format, list).utf8().get_data());
+ va_end(list);
+ }
+}
+
+void BindingsGenerator::_initialize() {
EditorHelp::generate_doc();
enum_types.clear();
+ _initialize_blacklisted_methods();
+
_populate_object_type_interfaces();
_populate_builtin_type_interfaces();
@@ -2888,41 +3004,49 @@ void BindingsGenerator::initialize() {
void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
const int NUM_OPTIONS = 2;
- int options_left = NUM_OPTIONS;
+ String generate_all_glue_option = "--generate-mono-glue";
+ String generate_cs_glue_option = "--generate-mono-cs-glue";
+ String generate_cpp_glue_option = "--generate-mono-cpp-glue";
- String mono_glue_option = "--generate-mono-glue";
- String cs_api_option = "--generate-cs-api";
+ String glue_dir_path;
+ String cs_dir_path;
+ String cpp_dir_path;
- verbose_output = true;
+ int options_left = NUM_OPTIONS;
const List<String>::Element *elem = p_cmdline_args.front();
while (elem && options_left) {
-
- if (elem->get() == mono_glue_option) {
-
+ if (elem->get() == generate_all_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
- if (get_singleton()->generate_glue(path_elem->get()) != OK)
- ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue");
+ glue_dir_path = path_elem->get();
elem = elem->next();
} else {
- ERR_PRINTS(mono_glue_option + ": No output directory specified");
+ ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)");
}
--options_left;
+ } else if (elem->get() == generate_cs_glue_option) {
+ const List<String>::Element *path_elem = elem->next();
- } else if (elem->get() == cs_api_option) {
+ if (path_elem) {
+ cs_dir_path = path_elem->get();
+ elem = elem->next();
+ } else {
+ ERR_PRINTS(generate_cs_glue_option + ": No output directory specified");
+ }
+ --options_left;
+ } else if (elem->get() == generate_cpp_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
- if (get_singleton()->generate_cs_api(path_elem->get()) != OK)
- ERR_PRINTS(cs_api_option + ": Failed to generate the C# API");
+ cpp_dir_path = path_elem->get();
elem = elem->next();
} else {
- ERR_PRINTS(cs_api_option + ": No output directory specified");
+ ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified");
}
--options_left;
@@ -2931,10 +3055,31 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
elem = elem->next();
}
- verbose_output = false;
+ if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) {
+ BindingsGenerator bindings_generator;
+ bindings_generator.set_log_print_enabled(true);
- if (options_left != NUM_OPTIONS)
+ if (glue_dir_path.length()) {
+ if (bindings_generator.generate_glue(glue_dir_path) != OK)
+ ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue");
+
+ if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK)
+ ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API");
+ }
+
+ if (cs_dir_path.length()) {
+ if (bindings_generator.generate_cs_api(cs_dir_path) != OK)
+ ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API");
+ }
+
+ if (cpp_dir_path.length()) {
+ if (bindings_generator.generate_glue(cpp_dir_path) != OK)
+ ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue");
+ }
+
+ // Exit once done
::exit(0);
+ }
}
#endif
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 42071f9c0d..8be51a6c55 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -32,7 +32,7 @@
#define BINDINGS_GENERATOR_H
#include "core/class_db.h"
-#include "dotnet_solution.h"
+#include "core/string_builder.h"
#include "editor/doc/doc_data.h"
#include "editor/editor_help.h"
@@ -158,17 +158,20 @@ class BindingsGenerator {
const DocData::MethodDoc *method_doc;
+ bool is_deprecated;
+ String deprecation_message;
+
void add_argument(const ArgumentInterface &argument) {
arguments.push_back(argument);
}
MethodInterface() {
- return_type.cname = BindingsGenerator::get_singleton()->name_cache.type_void;
is_vararg = false;
is_virtual = false;
requires_object_call = false;
is_internal = false;
method_doc = NULL;
+ is_deprecated = false;
}
};
@@ -399,8 +402,8 @@ class BindingsGenerator {
}
static void postsetup_enum_type(TypeInterface &r_enum_itype) {
- // C interface is the same as that of 'int'. Remember to apply any
- // changes done here to the 'int' type interface as well
+ // C interface for enums is the same as that of 'uint32_t'. Remember to apply
+ // any of the changes done here to the 'uint32_t' type interface as well.
r_enum_itype.c_arg_in = "&%s_in";
{
@@ -468,7 +471,7 @@ class BindingsGenerator {
}
};
- static bool verbose_output;
+ bool log_print_enabled;
OrderedHashMap<StringName, TypeInterface> obj_types;
@@ -487,9 +490,12 @@ class BindingsGenerator {
List<InternalCall> core_custom_icalls;
List<InternalCall> editor_custom_icalls;
+ Map<StringName, List<StringName> > blacklisted_methods;
+
+ void _initialize_blacklisted_methods();
+
struct NameCache {
StringName type_void;
- StringName type_int;
StringName type_Array;
StringName type_Dictionary;
StringName type_Variant;
@@ -500,9 +506,19 @@ class BindingsGenerator {
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_int = StaticCString::create("int");
type_Array = StaticCString::create("Array");
type_Dictionary = StaticCString::create("Dictionary");
type_Variant = StaticCString::create("Variant");
@@ -512,8 +528,20 @@ class BindingsGenerator {
type_String = StaticCString::create("String");
type_at_GlobalScope = StaticCString::create("@GlobalScope");
enum_Error = StaticCString::create("Error");
+
+ type_sbyte = StaticCString::create("sbyte");
+ type_short = StaticCString::create("short");
+ type_int = StaticCString::create("int");
+ type_long = StaticCString::create("long");
+ type_byte = StaticCString::create("byte");
+ type_ushort = StaticCString::create("ushort");
+ type_uint = StaticCString::create("uint");
+ type_ulong = StaticCString::create("ulong");
+ type_float = StaticCString::create("float");
+ type_double = StaticCString::create("double");
}
+ private:
NameCache(const NameCache &);
NameCache &operator=(const NameCache &);
};
@@ -559,6 +587,9 @@ class BindingsGenerator {
const TypeInterface *_get_type_or_null(const TypeReference &p_typeref);
const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref);
+ StringName _get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
+ StringName _get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
+
void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
void _populate_object_type_interfaces();
@@ -568,42 +599,36 @@ class BindingsGenerator {
Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file);
- Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, List<String> &p_output);
- Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, List<String> &p_output);
+ Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output);
+ Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output);
- void _generate_global_constants(List<String> &p_output);
+ void _generate_global_constants(StringBuilder &p_output);
- Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, List<String> &p_output);
+ Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output);
- Error _save_file(const String &p_path, const List<String> &p_content);
+ Error _save_file(const String &p_path, const StringBuilder &p_content);
- BindingsGenerator() {}
+ void _log(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
- BindingsGenerator(const BindingsGenerator &);
- BindingsGenerator &operator=(const BindingsGenerator &);
-
- friend class CSharpLanguage;
- static BindingsGenerator *singleton;
+ void _initialize();
public:
- Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true);
- Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true);
- Error generate_cs_api(const String &p_output_dir, bool p_verbose_output = true);
+ Error generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_files);
+ Error generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items);
+ Error generate_cs_api(const String &p_output_dir);
Error generate_glue(const String &p_output_dir);
+ _FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; }
+ _FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
+
static uint32_t get_version();
- void initialize();
+ static void handle_cmdline_args(const List<String> &p_cmdline_args);
- _FORCE_INLINE_ static BindingsGenerator *get_singleton() {
- if (!singleton) {
- singleton = memnew(BindingsGenerator);
- singleton->initialize();
- }
- return singleton;
+ BindingsGenerator() :
+ log_print_enabled(true) {
+ _initialize();
}
-
- static void handle_cmdline_args(const List<String> &p_cmdline_args);
};
#endif
diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp
index beeff51bc2..d88b08c646 100644
--- a/modules/mono/editor/csharp_project.cpp
+++ b/modules/mono/editor/csharp_project.cpp
@@ -44,66 +44,54 @@
namespace CSharpProject {
-String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) {
-
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
-
- GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
-
- Variant dir = p_dir;
- Variant compile_items = p_files;
- const Variant *args[2] = { &dir, &compile_items };
+bool generate_api_solution_impl(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
+ const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items,
+ GDMonoAssembly *p_tools_project_editor_assembly) {
+
+ GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator");
+
+ Variant solution_dir = p_solution_dir;
+ Variant core_proj_dir = p_core_proj_dir;
+ Variant core_compile_items = p_core_compile_items;
+ Variant editor_proj_dir = p_editor_proj_dir;
+ Variant editor_compile_items = p_editor_compile_items;
+ const Variant *args[5] = { &solution_dir, &core_proj_dir, &core_compile_items, &editor_proj_dir, &editor_compile_items };
MonoException *exc = NULL;
- MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &exc);
+ klass->get_method("GenerateApiSolution", 5)->invoke(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL_V(String());
+ ERR_FAIL_V(false);
}
- return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
+ return true;
}
-String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files) {
-
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
-
- GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
-
- Variant dir = p_dir;
- Variant core_proj_path = p_core_proj_path;
- Variant compile_items = p_files;
- const Variant *args[3] = { &dir, &core_proj_path, &compile_items };
- MonoException *exc = NULL;
- MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL_V(String());
- }
-
- return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
-}
+bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
+ const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items) {
-String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) {
+ if (GDMono::get_singleton()->get_tools_project_editor_assembly()) {
+ return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items,
+ p_editor_proj_dir, p_editor_compile_items,
+ GDMono::get_singleton()->get_tools_project_editor_assembly());
+ } else {
+ MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain");
+ CRASH_COND(temp_domain == NULL);
+ _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
+ _GDMONO_SCOPE_DOMAIN_(temp_domain);
- GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
+ GDMonoAssembly *tools_project_editor_assembly = NULL;
- Variant dir = p_dir;
- Variant name = p_name;
- Variant compile_items = p_files;
- const Variant *args[3] = { &dir, &name, &compile_items };
- MonoException *exc = NULL;
- MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &exc);
+ if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) {
+ ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'");
+ ERR_FAIL_V(false);
+ }
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL_V(String());
+ return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items,
+ p_editor_proj_dir, p_editor_compile_items,
+ tools_project_editor_assembly);
}
-
- return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
}
void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) {
@@ -111,9 +99,9 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
if (!GLOBAL_DEF("mono/project/auto_update_project", true))
return;
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
+ GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
- GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils");
+ GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils");
Variant project_path = p_project_path;
Variant item_type = p_item_type;
@@ -128,126 +116,4 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
}
}
-Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) {
-
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
-
- if (FileAccess::exists(p_output_path)) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- Error rm_err = da->remove(p_output_path);
-
- ERR_EXPLAIN("Failed to remove old scripts metadata file");
- ERR_FAIL_COND_V(rm_err != OK, rm_err);
- }
-
- GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils");
-
- void *args[2] = {
- GDMonoMarshal::mono_string_from_godot(p_project_path),
- GDMonoMarshal::mono_string_from_godot("Compile")
- };
-
- MonoException *exc = NULL;
- MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL_V(FAILED);
- }
-
- PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret);
- PoolStringArray::Read r = project_files.read();
-
- Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata();
- Dictionary new_dict;
-
- for (int i = 0; i < project_files.size(); i++) {
- const String &project_file = ("res://" + r[i]).simplify_path();
-
- uint64_t modified_time = FileAccess::get_modified_time(project_file);
-
- const Variant *old_file_var = old_dict.getptr(project_file);
- if (old_file_var) {
- Dictionary old_file_dict = old_file_var->operator Dictionary();
-
- if (old_file_dict["modified_time"].operator uint64_t() == modified_time) {
- // No changes so no need to parse again
- new_dict[project_file] = old_file_dict;
- continue;
- }
- }
-
- ScriptClassParser scp;
- Error err = scp.parse_file(project_file);
- if (err != OK) {
- ERR_PRINTS("Parse error: " + scp.get_error());
- ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file);
- ERR_FAIL_V(err);
- }
-
- Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes();
-
- bool found = false;
- Dictionary class_dict;
-
- String search_name = project_file.get_file().get_basename();
-
- for (int j = 0; j < classes.size(); j++) {
- const ScriptClassParser::ClassDecl &class_decl = classes[j];
-
- if (class_decl.base.size() == 0)
- continue; // Does not inherit nor implement anything, so it can't be a script class
-
- String class_cmp;
-
- if (class_decl.nested) {
- class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1);
- } else {
- class_cmp = class_decl.name;
- }
-
- if (class_cmp != search_name)
- continue;
-
- class_dict["namespace"] = class_decl.namespace_;
- class_dict["class_name"] = class_decl.name;
- class_dict["nested"] = class_decl.nested;
-
- found = true;
- break;
- }
-
- if (found) {
- Dictionary file_dict;
- file_dict["modified_time"] = modified_time;
- file_dict["class"] = class_dict;
- new_dict[project_file] = file_dict;
- }
- }
-
- if (new_dict.size()) {
- String json = JSON::print(new_dict, "", false);
-
- String base_dir = p_output_path.get_base_dir();
-
- if (!DirAccess::exists(base_dir)) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
-
- Error err = da->make_dir_recursive(base_dir);
- ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
- }
-
- Error ferr;
- FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr);
- ERR_EXPLAIN("Cannot open file for writing: " + p_output_path);
- ERR_FAIL_COND_V(ferr != OK, ferr);
- f->store_string(json);
- f->flush();
- f->close();
- memdelete(f);
- }
-
- return OK;
-}
-
} // namespace CSharpProject
diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h
index 3d5a65f8da..b42762cea2 100644
--- a/modules/mono/editor/csharp_project.h
+++ b/modules/mono/editor/csharp_project.h
@@ -35,14 +35,11 @@
namespace CSharpProject {
-String generate_core_api_project(const String &p_dir, const Vector<String> &p_files = Vector<String>());
-String generate_editor_api_project(const String &p_dir, const String &p_core_dll_path, const Vector<String> &p_files = Vector<String>());
-String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>());
+bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
+ const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items);
void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
-Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path);
-
} // namespace CSharpProject
#endif // CSHARP_PROJECT_H
diff --git a/modules/mono/editor/dotnet_solution.cpp b/modules/mono/editor/dotnet_solution.cpp
deleted file mode 100644
index 324752cafc..0000000000
--- a/modules/mono/editor/dotnet_solution.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-/*************************************************************************/
-/* dotnet_solution.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 "dotnet_solution.h"
-
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-
-#include "../utils/path_utils.h"
-#include "../utils/string_utils.h"
-#include "csharp_project.h"
-
-#define SOLUTION_TEMPLATE \
- "Microsoft Visual Studio Solution File, Format Version 12.00\n" \
- "# Visual Studio 2012\n" \
- "%0\n" \
- "Global\n" \
- "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n" \
- "%1\n" \
- "\tEndGlobalSection\n" \
- "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n" \
- "%2\n" \
- "\tEndGlobalSection\n" \
- "EndGlobal\n"
-
-#define PROJECT_DECLARATION "Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"%0\", \"%1\", \"{%2}\"\nEndProject"
-
-#define SOLUTION_PLATFORMS_CONFIG "\t%0|Any CPU = %0|Any CPU"
-
-#define PROJECT_PLATFORMS_CONFIG \
- "\t\t{%0}.%1|Any CPU.ActiveCfg = %1|Any CPU\n" \
- "\t\t{%0}.%1|Any CPU.Build.0 = %1|Any CPU"
-
-void DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) {
- projects[p_name] = p_project_info;
-}
-
-bool DotNetSolution::has_project(const String &p_name) const {
- return projects.find(p_name) != NULL;
-}
-
-const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const {
- return projects[p_name];
-}
-
-bool DotNetSolution::remove_project(const String &p_name) {
- return projects.erase(p_name);
-}
-
-Error DotNetSolution::save() {
- bool dir_exists = DirAccess::exists(path);
- ERR_EXPLAIN("The directory does not exist.");
- ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND);
-
- String projs_decl;
- String sln_platform_cfg;
- String proj_platform_cfg;
-
- for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) {
- const String &name = E->key();
- const ProjectInfo &proj_info = E->value();
-
- bool is_front = E == projects.front();
-
- if (!is_front)
- projs_decl += "\n";
-
- projs_decl += sformat(PROJECT_DECLARATION, name, proj_info.relpath.replace("/", "\\"), proj_info.guid);
-
- for (int i = 0; i < proj_info.configs.size(); i++) {
- const String &config = proj_info.configs[i];
-
- if (i != 0 || !is_front) {
- sln_platform_cfg += "\n";
- proj_platform_cfg += "\n";
- }
-
- sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config);
- proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, proj_info.guid, config);
- }
- }
-
- String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg);
-
- FileAccess *file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE);
- ERR_FAIL_NULL_V(file, ERR_FILE_CANT_WRITE);
- file->store_string(content);
- file->close();
- memdelete(file);
-
- return OK;
-}
-
-bool DotNetSolution::set_path(const String &p_existing_path) {
- if (p_existing_path.is_abs_path()) {
- path = p_existing_path;
- } else {
- String abspath;
- if (!rel_path_to_abs(p_existing_path, abspath))
- return false;
- path = abspath;
- }
-
- return true;
-}
-
-String DotNetSolution::get_path() {
- return path;
-}
-
-DotNetSolution::DotNetSolution(const String &p_name) {
- name = p_name;
-}
diff --git a/modules/mono/editor/dotnet_solution.h b/modules/mono/editor/dotnet_solution.h
deleted file mode 100644
index 18933364fa..0000000000
--- a/modules/mono/editor/dotnet_solution.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*************************************************************************/
-/* dotnet_solution.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 NET_SOLUTION_H
-#define NET_SOLUTION_H
-
-#include "core/map.h"
-#include "core/ustring.h"
-
-struct DotNetSolution {
- String name;
-
- struct ProjectInfo {
- String guid;
- String relpath; // Must be relative to the solution directory
- Vector<String> configs;
- };
-
- void add_new_project(const String &p_name, const ProjectInfo &p_project_info);
- bool has_project(const String &p_name) const;
- const ProjectInfo &get_project_info(const String &p_name) const;
- bool remove_project(const String &p_name);
-
- Error save();
-
- bool set_path(const String &p_existing_path);
- String get_path();
-
- DotNetSolution(const String &p_name);
-
-private:
- String path;
- Map<String, ProjectInfo> projects;
-};
-
-#endif // NET_SOLUTION_H
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
new file mode 100644
index 0000000000..a3b5b450ef
--- /dev/null
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -0,0 +1,429 @@
+/*************************************************************************/
+/* editor_internal_calls.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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 "editor_internal_calls.h"
+
+#include "core/message_queue.h"
+#include "core/os/os.h"
+#include "core/version.h"
+#include "editor/editor_node.h"
+#include "editor/plugins/script_editor_plugin.h"
+#include "editor/script_editor_debugger.h"
+#include "main/main.h"
+
+#include "../csharp_script.h"
+#include "../glue/cs_glue_version.gen.h"
+#include "../godotsharp_dirs.h"
+#include "../mono_gd/gd_mono_marshal.h"
+#include "../utils/osx_utils.h"
+#include "bindings_generator.h"
+#include "godotsharp_export.h"
+#include "script_class_parser.h"
+
+MonoString *godot_icall_GodotSharpDirs_ResDataDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_ResConfigDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_ResTempDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_MonoUserDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() {
+#ifdef TOOLS_ENABLED
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir());
+#else
+ return NULL;
+#endif
+}
+
+MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() {
+#ifdef TOOLS_ENABLED
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir());
+#else
+ return NULL;
+#endif
+}
+
+MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() {
+#ifdef TOOLS_ENABLED
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path());
+#else
+ return NULL;
+#endif
+}
+
+MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() {
+#ifdef TOOLS_ENABLED
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path());
+#else
+ return NULL;
+#endif
+}
+
+MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() {
+#ifdef TOOLS_ENABLED
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir());
+#else
+ return NULL;
+#endif
+}
+
+MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() {
+#ifdef TOOLS_ENABLED
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir());
+#else
+ return NULL;
+#endif
+}
+
+MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() {
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir());
+}
+
+MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() {
+#ifdef WINDOWS_ENABLED
+ return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir());
+#else
+ return NULL;
+#endif
+}
+
+void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) {
+ String task = GDMonoMarshal::mono_string_to_godot(p_task);
+ String label = GDMonoMarshal::mono_string_to_godot(p_label);
+ EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel);
+}
+
+void godot_icall_EditorProgress_Dispose(MonoString *p_task) {
+ String task = GDMonoMarshal::mono_string_to_godot(p_task);
+ EditorNode::progress_end_task(task);
+}
+
+MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) {
+ String task = GDMonoMarshal::mono_string_to_godot(p_task);
+ String state = GDMonoMarshal::mono_string_to_godot(p_state);
+ return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh);
+}
+
+BindingsGenerator *godot_icall_BindingsGenerator_Ctor() {
+ return memnew(BindingsGenerator);
+}
+
+void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) {
+ memdelete(p_handle);
+}
+
+MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) {
+ return p_handle->is_log_print_enabled();
+}
+
+void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) {
+ p_handle.set_log_print_enabled(p_enabled);
+}
+
+int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) {
+ String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir);
+ return p_handle->generate_cs_api(output_dir);
+}
+
+uint32_t godot_icall_BindingsGenerator_Version() {
+ return BindingsGenerator::get_version();
+}
+
+uint32_t godot_icall_BindingsGenerator_CsGlueVersion() {
+ return CS_GLUE_VERSION;
+}
+
+int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes) {
+ String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath);
+
+ ScriptClassParser scp;
+ Error err = scp.parse_file(filepath);
+ if (err == OK) {
+ Array classes = GDMonoMarshal::mono_object_to_variant(p_classes);
+ const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes();
+
+ for (int i = 0; i < class_decls.size(); i++) {
+ const ScriptClassParser::ClassDecl &classDecl = class_decls[i];
+
+ Dictionary classDeclDict;
+ classDeclDict["name"] = classDecl.name;
+ classDeclDict["namespace"] = classDecl.namespace_;
+ classDeclDict["nested"] = classDecl.nested;
+ classDeclDict["base_count"] = classDecl.base.size();
+ classes.push_back(classDeclDict);
+ }
+ }
+ return err;
+}
+
+uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path,
+ MonoString *p_build_config, MonoString *p_custom_lib_dir, MonoObject *r_dependencies) {
+ String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name);
+ String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path);
+ String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
+ String custom_lib_dir = GDMonoMarshal::mono_string_to_godot(p_custom_lib_dir);
+ Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies);
+
+ return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies);
+}
+
+float godot_icall_Internal_EditorScale() {
+ return EDSCALE;
+}
+
+MonoObject *godot_icall_Internal_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
+ String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
+ Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
+ Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
+ return GDMonoMarshal::variant_to_mono_object(result);
+}
+
+MonoObject *godot_icall_Internal_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
+ String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
+ Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
+ Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
+ return GDMonoMarshal::variant_to_mono_object(result);
+}
+
+MonoString *godot_icall_Internal_FullTemplatesDir() {
+ String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
+ return GDMonoMarshal::mono_string_from_godot(full_templates_dir);
+}
+
+MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) {
+ String path = GDMonoMarshal::mono_string_to_godot(p_path);
+ return GDMonoMarshal::mono_string_from_godot(path.simplify_path());
+}
+
+MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) {
+#ifdef OSX_ENABLED
+ String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id);
+ return (MonoBoolean)osx_is_app_bundle_installed;
+#else
+ (void)p_bundle_id; // UNUSED
+ return (MonoBoolean) false;
+#endif
+}
+
+MonoBoolean godot_icall_Internal_MetadataIsApiAssemblyInvalidated(int32_t p_api_type) {
+ return GDMono::get_singleton()->metadata_is_api_assembly_invalidated((APIAssembly::Type)p_api_type);
+}
+
+void godot_icall_Internal_MetadataSetApiAssemblyInvalidated(int32_t p_api_type, MonoBoolean p_invalidated) {
+ GDMono::get_singleton()->metadata_set_api_assembly_invalidated((APIAssembly::Type)p_api_type, (bool)p_invalidated);
+}
+
+MonoBoolean godot_icall_Internal_IsMessageQueueFlushing() {
+ return (MonoBoolean)MessageQueue::get_singleton()->is_flushing();
+}
+
+MonoBoolean godot_icall_Internal_GodotIs32Bits() {
+ return sizeof(void *) == 4;
+}
+
+MonoBoolean godot_icall_Internal_GodotIsRealTDouble() {
+#ifdef REAL_T_IS_DOUBLE
+ return (MonoBoolean) true;
+#else
+ return (MonoBoolean) false;
+#endif
+}
+
+void godot_icall_Internal_GodotMainIteration() {
+ Main::iteration();
+}
+
+uint64_t godot_icall_Internal_GetCoreApiHash() {
+ return ClassDB::get_api_hash(ClassDB::API_CORE);
+}
+
+uint64_t godot_icall_Internal_GetEditorApiHash() {
+ return ClassDB::get_api_hash(ClassDB::API_EDITOR);
+}
+
+MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() {
+#ifdef GD_MONO_HOT_RELOAD
+ return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed();
+#else
+ return (MonoBoolean) false;
+#endif
+}
+
+void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) {
+#ifdef GD_MONO_HOT_RELOAD
+ _GodotSharp::get_singleton()->call_deferred("_reload_assemblies", (bool)p_soft_reload);
+#endif
+}
+
+void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() {
+ ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
+}
+
+MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) {
+ Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource);
+ return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus);
+}
+
+void godot_icall_Internal_EditorNodeShowScriptScreen() {
+ EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
+}
+
+MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) {
+ Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing();
+
+ MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype);
+
+ uint32_t type_encoding = mono_type_get_type(dict_type);
+ MonoClass *type_class_raw = mono_class_from_mono_type(dict_type);
+ GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw);
+
+ return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class));
+}
+
+MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
+#ifdef WINDOWS_ENABLED
+ String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
+ return GDMonoMarshal::mono_string_from_godot(install_root_dir);
+#else
+ return NULL;
+#endif
+}
+
+MonoString *godot_icall_Utils_OS_GetPlatformName() {
+ String os_name = OS::get_singleton()->get_name();
+ return GDMonoMarshal::mono_string_from_godot(os_name);
+}
+
+void register_editor_internal_calls() {
+
+ // GodotSharpDirs
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir);
+ mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir);
+
+ // EditorProgress
+ mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create);
+ mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose);
+ mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step);
+
+ // BiningsGenerator
+ mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor);
+ mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor);
+ mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled);
+ mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled);
+ mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi);
+ mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version);
+ mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion);
+
+ // ScriptClassParser
+ mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile);
+
+ // GodotSharpExport
+ mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies);
+
+ // Internals
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorScale", (void *)godot_icall_Internal_EditorScale);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GlobalDef", (void *)godot_icall_Internal_GlobalDef);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorDef", (void *)godot_icall_Internal_EditorDef);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataIsApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataIsApiAssemblyInvalidated);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataSetApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataSetApiAssemblyInvalidated);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_IsMessageQueueFlushing", (void *)godot_icall_Internal_IsMessageQueueFlushing);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot);
+
+ // Utils.OS
+ mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
+}
diff --git a/modules/mono/editor/monodevelop_instance.h b/modules/mono/editor/editor_internal_calls.h
index 3b3af9607b..1682da66e5 100644
--- a/modules/mono/editor/monodevelop_instance.h
+++ b/modules/mono/editor/editor_internal_calls.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* monodevelop_instance.h */
+/* editor_internal_calls.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,29 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef MONODEVELOP_INSTANCE_H
-#define MONODEVELOP_INSTANCE_H
+#ifndef EDITOR_INTERNAL_CALL_H
+#define EDITOR_INTERNAL_CALL_H
-#include "core/reference.h"
+void register_editor_internal_calls();
-#include "../mono_gc_handle.h"
-#include "../mono_gd/gd_mono_method.h"
-
-class MonoDevelopInstance {
-
- Ref<MonoGCHandle> gc_handle;
- GDMonoMethod *execute_method;
-
-public:
- enum EditorId {
- MONODEVELOP = 0,
- VISUALSTUDIO_FOR_MAC = 1
- };
-
- void execute(const Vector<String> &p_files);
- void execute(const String &p_file);
-
- MonoDevelopInstance(const String &p_solution, EditorId p_editor_id);
-};
-
-#endif // MONODEVELOP_INSTANCE_H
+#endif // EDITOR_INTERNAL_CALL_H
diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp
deleted file mode 100644
index 00c780d1b7..0000000000
--- a/modules/mono/editor/godotsharp_builds.cpp
+++ /dev/null
@@ -1,602 +0,0 @@
-/*************************************************************************/
-/* godotsharp_builds.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 "godotsharp_builds.h"
-
-#include "core/vector.h"
-#include "main/main.h"
-
-#include "../glue/cs_glue_version.gen.h"
-#include "../godotsharp_dirs.h"
-#include "../mono_gd/gd_mono_class.h"
-#include "../mono_gd/gd_mono_marshal.h"
-#include "../utils/path_utils.h"
-#include "bindings_generator.h"
-#include "csharp_project.h"
-#include "godotsharp_editor.h"
-
-#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)"
-#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)"
-#define PROP_NAME_XBUILD "xbuild (Deprecated)"
-
-void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) {
-
- String solution = GDMonoMarshal::mono_string_to_godot(p_solution);
- String config = GDMonoMarshal::mono_string_to_godot(p_config);
- GodotSharpBuilds::get_singleton()->build_exit_callback(MonoBuildInfo(solution, config), p_exit_code);
-}
-
-static Vector<const char *> _get_msbuild_hint_dirs() {
- Vector<const char *> ret;
-#ifdef OSX_ENABLED
- ret.push_back("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
- ret.push_back("/usr/local/var/homebrew/linked/mono/bin/");
-#endif
- ret.push_back("/opt/novell/mono/bin/");
- return ret;
-}
-
-#ifdef UNIX_ENABLED
-String _find_build_engine_on_unix(const String &p_name) {
- String ret = path_which(p_name);
-
- if (ret.length())
- return ret;
-
- String ret_fallback = path_which(p_name + ".exe");
- if (ret_fallback.length())
- return ret_fallback;
-
- static Vector<const char *> locations = _get_msbuild_hint_dirs();
-
- for (int i = 0; i < locations.size(); i++) {
- String hint_path = locations[i] + p_name;
-
- if (FileAccess::exists(hint_path)) {
- return hint_path;
- }
- }
-
- return String();
-}
-#endif
-
-MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
-
- GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool")));
-
-#if defined(WINDOWS_ENABLED)
- switch (build_tool) {
- case GodotSharpBuilds::MSBUILD_VS: {
- static String msbuild_tools_path;
-
- if (msbuild_tools_path.empty() || !FileAccess::exists(msbuild_tools_path)) {
- // Try to search it again if it wasn't found last time or if it was removed from its location
- msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path();
-
- if (msbuild_tools_path.empty()) {
- ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Tried with path: " + msbuild_tools_path);
- return NULL;
- }
- }
-
- if (!msbuild_tools_path.ends_with("\\"))
- msbuild_tools_path += "\\";
-
- return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe");
- } break;
- case GodotSharpBuilds::MSBUILD_MONO: {
- String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat");
-
- if (!FileAccess::exists(msbuild_path)) {
- ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path);
- return NULL;
- }
-
- return GDMonoMarshal::mono_string_from_godot(msbuild_path);
- } break;
- case GodotSharpBuilds::XBUILD: {
- String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat");
-
- if (!FileAccess::exists(xbuild_path)) {
- ERR_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path);
- return NULL;
- }
-
- return GDMonoMarshal::mono_string_from_godot(xbuild_path);
- } break;
- default:
- ERR_EXPLAIN("You don't deserve to live");
- CRASH_NOW();
- }
-#elif defined(UNIX_ENABLED)
- static String msbuild_path;
- static String xbuild_path;
-
- if (build_tool == GodotSharpBuilds::XBUILD) {
- if (xbuild_path.empty() || !FileAccess::exists(xbuild_path)) {
- // Try to search it again if it wasn't found last time or if it was removed from its location
- xbuild_path = _find_build_engine_on_unix("msbuild");
- }
-
- if (xbuild_path.empty()) {
- ERR_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'");
- return NULL;
- }
- } else {
- if (msbuild_path.empty() || !FileAccess::exists(msbuild_path)) {
- // Try to search it again if it wasn't found last time or if it was removed from its location
- msbuild_path = _find_build_engine_on_unix("msbuild");
- }
-
- if (msbuild_path.empty()) {
- ERR_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'");
- return NULL;
- }
- }
-
- return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path);
-#else
- (void)build_tool; // UNUSED
-
- ERR_EXPLAIN("Not implemented on this platform");
- ERR_FAIL_V(NULL);
-#endif
-}
-
-MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() {
-
-#if defined(WINDOWS_ENABLED)
- const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info();
- if (mono_reg_info.bin_dir.length()) {
- return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir);
- }
-
- ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry");
- ERR_FAIL_V(NULL);
-#else
- return NULL;
-#endif
-}
-
-MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() {
-
-#if defined(WINDOWS_ENABLED)
- return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO;
-#else
- return false;
-#endif
-}
-
-MonoBoolean godot_icall_BuildInstance_get_PrintBuildOutput() {
-
- return (bool)EDITOR_GET("mono/builds/print_build_output");
-}
-
-void GodotSharpBuilds::register_internal_calls() {
-
- static bool registered = false;
- ERR_FAIL_COND(registered);
- registered = true;
-
- mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback);
- mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath);
- mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir);
- mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows);
- mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_PrintBuildOutput", (void *)godot_icall_BuildInstance_get_PrintBuildOutput);
-}
-
-void GodotSharpBuilds::show_build_error_dialog(const String &p_message) {
-
- GodotSharpEditor::get_singleton()->show_error_dialog(p_message, "Build error");
- MonoBottomPanel::get_singleton()->show_build_tab();
-}
-
-bool GodotSharpBuilds::build_api_sln(const String &p_api_sln_dir, const String &p_config) {
-
- String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
-
- String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
- String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
-
- String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
- String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
-
- if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) {
- MonoBuildInfo api_build_info(api_sln_file, p_config);
- // TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
- // once we start to actively document manually maintained C# classes
- api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings
-
- if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) {
- show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution.");
- return false;
- }
- }
-
- return true;
-}
-
-bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) {
-
- // Create destination directory if needed
- if (!DirAccess::exists(p_dst_dir)) {
- DirAccess *da = DirAccess::create_for_path(p_dst_dir);
- Error err = da->make_dir_recursive(p_dst_dir);
- memdelete(da);
-
- if (err != OK) {
- show_build_error_dialog("Failed to create destination directory for the API assemblies. Error: " + itos(err));
- return false;
- }
- }
-
- String assembly_file = p_assembly_name + ".dll";
- String assembly_src = p_src_dir.plus_file(assembly_file);
- String assembly_dst = p_dst_dir.plus_file(assembly_file);
-
- if (!FileAccess::exists(assembly_dst) ||
- FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
- GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
-
- String xml_file = p_assembly_name + ".xml";
- if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
- WARN_PRINTS("Failed to copy " + xml_file);
-
- String pdb_file = p_assembly_name + ".pdb";
- if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK)
- WARN_PRINTS("Failed to copy " + pdb_file);
-
- Error err = da->copy(assembly_src, assembly_dst);
-
- if (err != OK) {
- show_build_error_dialog("Failed to copy " + assembly_file);
- return false;
- }
-
- GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
- }
-
- return true;
-}
-
-String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) {
-
- uint64_t api_hash = p_api_type == APIAssembly::API_CORE ?
- GDMono::get_singleton()->get_api_core_hash() :
- GDMono::get_singleton()->get_api_editor_hash();
- return String::num_uint64(api_hash) +
- "_" + String::num_uint64(BindingsGenerator::get_version()) +
- "_" + String::num_uint64(CS_GLUE_VERSION);
-}
-
-bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) {
-
- String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
-
- String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir();
- String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
-
- if (FileAccess::exists(editor_prebuilt_api_dir.plus_file(api_name + ".dll"))) {
- EditorProgress pr("mono_copy_prebuilt_api_assembly", "Copying prebuilt " + api_name + " assembly...", 1);
- pr.step("Copying " + api_name + " assembly", 0);
- return GodotSharpBuilds::copy_api_assembly(editor_prebuilt_api_dir, res_assemblies_dir, api_name, p_api_type);
- }
-
- String api_build_config = "Release";
-
- EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3);
-
- pr.step("Generating " API_SOLUTION_NAME " solution", 0);
-
- String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
- .plus_file(_api_folder_name(APIAssembly::API_CORE));
-
- String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
-
- if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) {
- BindingsGenerator *gen = BindingsGenerator::get_singleton();
- bool gen_verbose = OS::get_singleton()->is_stdout_verbose();
-
- Error err = gen->generate_cs_api(api_sln_dir, gen_verbose);
- if (err != OK) {
- show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err));
- return false;
- }
- }
-
- pr.step("Building " API_SOLUTION_NAME " solution", 1);
-
- if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config))
- return false;
-
- pr.step("Copying " + api_name + " assembly", 2);
-
- // Copy the built assembly to the assemblies directory
- String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config);
- if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type))
- return false;
-
- return true;
-}
-
-bool GodotSharpBuilds::build_project_blocking(const String &p_config) {
-
- if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
- return true; // No solution to build
-
- if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
- return false;
-
- if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
- return false;
-
- EditorProgress pr("mono_project_debug_build", "Building project solution...", 1);
- pr.step("Building project solution", 0);
-
- MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config);
- if (!GodotSharpBuilds::get_singleton()->build(build_info)) {
- GodotSharpBuilds::show_build_error_dialog("Failed to build project solution");
- return false;
- }
-
- return true;
-}
-
-bool GodotSharpBuilds::editor_build_callback() {
-
- if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
- return true; // No solution to build
-
- String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
- String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player");
-
- Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor);
- ERR_FAIL_COND_V(metadata_err != OK, false);
-
- if (FileAccess::exists(scripts_metadata_path_editor)) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player);
-
- ERR_EXPLAIN("Failed to copy scripts metadata file");
- ERR_FAIL_COND_V(copy_err != OK, false);
- }
-
- return build_project_blocking("Tools");
-}
-
-GodotSharpBuilds *GodotSharpBuilds::singleton = NULL;
-
-void GodotSharpBuilds::build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code) {
-
- BuildProcess *match = builds.getptr(p_build_info);
- ERR_FAIL_NULL(match);
-
- BuildProcess &bp = *match;
- bp.on_exit(p_exit_code);
-}
-
-void GodotSharpBuilds::restart_build(MonoBuildTab *p_build_tab) {
-}
-
-void GodotSharpBuilds::stop_build(MonoBuildTab *p_build_tab) {
-}
-
-bool GodotSharpBuilds::build(const MonoBuildInfo &p_build_info) {
-
- BuildProcess *match = builds.getptr(p_build_info);
-
- if (match) {
- BuildProcess &bp = *match;
- bp.start(true);
- return bp.exit_code == 0;
- } else {
- BuildProcess bp = BuildProcess(p_build_info);
- bp.start(true);
- builds.set(p_build_info, bp);
- return bp.exit_code == 0;
- }
-}
-
-bool GodotSharpBuilds::build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) {
-
- BuildProcess *match = builds.getptr(p_build_info);
-
- if (match) {
- BuildProcess &bp = *match;
- bp.start();
- return !bp.exited; // failed to start
- } else {
- BuildProcess bp = BuildProcess(p_build_info, p_callback);
- bp.start();
- builds.set(p_build_info, bp);
- return !bp.exited; // failed to start
- }
-}
-
-GodotSharpBuilds::GodotSharpBuilds() {
-
- singleton = this;
-
- EditorNode::get_singleton()->add_build_callback(&GodotSharpBuilds::editor_build_callback);
-
- // Build tool settings
- EditorSettings *ed_settings = EditorSettings::get_singleton();
-
-#ifdef WINDOWS_ENABLED
- EDITOR_DEF("mono/builds/build_tool", MSBUILD_VS);
-#else
- EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO);
-#endif
-
- ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM,
- PROP_NAME_MSBUILD_MONO
-#ifdef WINDOWS_ENABLED
- "," PROP_NAME_MSBUILD_VS
-#endif
- "," PROP_NAME_XBUILD));
-
- EDITOR_DEF("mono/builds/print_build_output", false);
-}
-
-GodotSharpBuilds::~GodotSharpBuilds() {
-
- singleton = NULL;
-}
-
-void GodotSharpBuilds::BuildProcess::on_exit(int p_exit_code) {
-
- exited = true;
- exit_code = p_exit_code;
- build_tab->on_build_exit(p_exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR);
- build_instance.unref();
-
- if (exit_callback)
- exit_callback(exit_code);
-}
-
-void GodotSharpBuilds::BuildProcess::start(bool p_blocking) {
-
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
-
- exit_code = -1;
-
- String log_dirpath = build_info.get_log_dirpath();
-
- if (build_tab) {
- build_tab->on_build_start();
- } else {
- build_tab = memnew(MonoBuildTab(build_info, log_dirpath));
- MonoBottomPanel::get_singleton()->add_build_tab(build_tab);
- }
-
- if (p_blocking) {
- // Required in order to update the build tasks list
- Main::iteration();
- }
-
- if (!exited) {
- exited = true;
- String message = "Tried to start build process, but it is already running";
- build_tab->on_build_exec_failed(message);
- ERR_EXPLAIN(message);
- ERR_FAIL();
- }
-
- exited = false;
-
- // Remove old issues file
-
- String issues_file = get_msbuild_issues_filename();
- DirAccessRef d = DirAccess::create_for_path(log_dirpath);
- if (d->file_exists(issues_file)) {
- Error err = d->remove(issues_file);
- if (err != OK) {
- exited = true;
- String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file);
- String message = "Cannot remove issues file: " + file_path;
- build_tab->on_build_exec_failed(message);
- ERR_EXPLAIN(message);
- ERR_FAIL();
- }
- }
-
- GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Build", "BuildInstance");
-
- MonoObject *mono_object = mono_object_new(mono_domain_get(), klass->get_mono_ptr());
-
- // Construct
-
- Variant solution = build_info.solution;
- Variant config = build_info.configuration;
-
- const Variant *ctor_args[2] = { &solution, &config };
-
- MonoException *exc = NULL;
- GDMonoMethod *ctor = klass->get_method(".ctor", 2);
- ctor->invoke(mono_object, ctor_args, &exc);
-
- if (exc) {
- exited = true;
- GDMonoUtils::debug_unhandled_exception(exc);
- String message = "The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
- build_tab->on_build_exec_failed(message);
- ERR_EXPLAIN(message);
- ERR_FAIL();
- }
-
- // Call Build
-
- String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path();
- Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path);
- Variant logger_output_dir = log_dirpath;
- Variant custom_props = build_info.custom_props;
-
- const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props };
-
- exc = NULL;
- GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3);
- build_method->invoke(mono_object, args, &exc);
-
- if (exc) {
- exited = true;
- GDMonoUtils::debug_unhandled_exception(exc);
- String message = "The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
- build_tab->on_build_exec_failed(message);
- ERR_EXPLAIN(message);
- ERR_FAIL();
- }
-
- // Build returned
-
- if (p_blocking) {
- exited = true;
- exit_code = klass->get_field("exitCode")->get_int_value(mono_object);
-
- if (exit_code != 0) {
- String log_filepath = build_info.get_log_dirpath().plus_file(get_msbuild_log_filename());
- print_verbose("MSBuild exited with code: " + itos(exit_code) + ". Log file: " + log_filepath);
- }
-
- build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR);
- } else {
- build_instance = MonoGCHandle::create_strong(mono_object);
- exited = false;
- }
-}
-
-GodotSharpBuilds::BuildProcess::BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) :
- build_info(p_build_info),
- build_tab(NULL),
- exit_callback(p_callback),
- exited(true),
- exit_code(-1) {
-}
diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h
deleted file mode 100644
index 652d30538a..0000000000
--- a/modules/mono/editor/godotsharp_builds.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/*************************************************************************/
-/* godotsharp_builds.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 GODOTSHARP_BUILDS_H
-#define GODOTSHARP_BUILDS_H
-
-#include "../mono_gd/gd_mono.h"
-#include "mono_bottom_panel.h"
-#include "mono_build_info.h"
-
-typedef void (*GodotSharpBuild_ExitCallback)(int);
-
-class GodotSharpBuilds {
-
-private:
- struct BuildProcess {
- Ref<MonoGCHandle> build_instance;
- MonoBuildInfo build_info;
- MonoBuildTab *build_tab;
- GodotSharpBuild_ExitCallback exit_callback;
- bool exited;
- int exit_code;
-
- void on_exit(int p_exit_code);
- void start(bool p_blocking = false);
-
- BuildProcess() {}
- BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL);
- };
-
- HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds;
-
- static String _api_folder_name(APIAssembly::Type p_api_type);
-
- static GodotSharpBuilds *singleton;
-
-public:
- enum BuildTool {
- MSBUILD_MONO,
-#ifdef WINDOWS_ENABLED
- MSBUILD_VS,
-#endif
- XBUILD // Deprecated
- };
-
- _FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; }
-
- static void register_internal_calls();
-
- static void show_build_error_dialog(const String &p_message);
-
- static const char *get_msbuild_issues_filename() { return "msbuild_issues.csv"; }
- static const char *get_msbuild_log_filename() { return "msbuild_log.txt"; }
-
- void build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code);
-
- void restart_build(MonoBuildTab *p_build_tab);
- void stop_build(MonoBuildTab *p_build_tab);
-
- bool build(const MonoBuildInfo &p_build_info);
- bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL);
-
- static bool build_api_sln(const String &p_api_sln_dir, const String &p_config);
- static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type);
-
- static bool make_api_assembly(APIAssembly::Type p_api_type);
-
- static bool build_project_blocking(const String &p_config);
-
- static bool editor_build_callback();
-
- GodotSharpBuilds();
- ~GodotSharpBuilds();
-};
-
-#endif // GODOTSHARP_BUILDS_H
diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp
deleted file mode 100644
index 9d42528927..0000000000
--- a/modules/mono/editor/godotsharp_editor.cpp
+++ /dev/null
@@ -1,581 +0,0 @@
-/*************************************************************************/
-/* godotsharp_editor.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 "godotsharp_editor.h"
-
-#include "core/message_queue.h"
-#include "core/os/os.h"
-#include "core/project_settings.h"
-#include "scene/gui/control.h"
-#include "scene/main/node.h"
-
-#include "../csharp_script.h"
-#include "../godotsharp_dirs.h"
-#include "../mono_gd/gd_mono.h"
-#include "../mono_gd/gd_mono_marshal.h"
-#include "../utils/path_utils.h"
-#include "bindings_generator.h"
-#include "csharp_project.h"
-#include "dotnet_solution.h"
-#include "godotsharp_export.h"
-
-#ifdef OSX_ENABLED
-#include "../utils/osx_utils.h"
-#endif
-
-#ifdef WINDOWS_ENABLED
-#include "../utils/mono_reg_utils.h"
-#endif
-
-GodotSharpEditor *GodotSharpEditor::singleton = NULL;
-
-bool GodotSharpEditor::_create_project_solution() {
-
- EditorProgress pr("create_csharp_solution", TTR("Generating solution..."), 2);
-
- pr.step(TTR("Generating C# project..."));
-
- String path = OS::get_singleton()->get_resource_dir();
- String name = ProjectSettings::get_singleton()->get("application/config/name");
- if (name.empty()) {
- name = "UnnamedProject";
- }
-
- String guid = CSharpProject::generate_game_project(path, name);
-
- if (guid.length()) {
-
- DotNetSolution solution(name);
-
- if (!solution.set_path(path)) {
- show_error_dialog(TTR("Failed to create solution."));
- return false;
- }
-
- DotNetSolution::ProjectInfo proj_info;
- proj_info.guid = guid;
- proj_info.relpath = name + ".csproj";
- proj_info.configs.push_back("Debug");
- proj_info.configs.push_back("Release");
- proj_info.configs.push_back("Tools");
-
- solution.add_new_project(name, proj_info);
-
- Error sln_error = solution.save();
-
- if (sln_error != OK) {
- show_error_dialog(TTR("Failed to save solution."));
- return false;
- }
-
- if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
- return false;
-
- if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
- return false;
-
- pr.step(TTR("Done"));
-
- // Here, after all calls to progress_task_step
- call_deferred("_remove_create_sln_menu_option");
-
- } else {
- show_error_dialog(TTR("Failed to create C# project."));
- }
-
- return true;
-}
-
-void GodotSharpEditor::_make_api_solutions_if_needed() {
- // I'm sick entirely of ProgressDialog
-
- static int attempts_left = 100;
-
- if (MessageQueue::get_singleton()->is_flushing() || !SceneTree::get_singleton()) {
- ERR_FAIL_COND(attempts_left == 0); // You've got to be kidding
-
- if (SceneTree::get_singleton()) {
- SceneTree::get_singleton()->connect("idle_frame", this, "_make_api_solutions_if_needed", Vector<Variant>());
- } else {
- call_deferred("_make_api_solutions_if_needed");
- }
-
- attempts_left--;
- return;
- }
-
- // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike
- // the message queue, with signals the collateral damage should be minimal in the worst case.
- static bool recursion_guard = false;
- if (!recursion_guard) {
- recursion_guard = true;
-
- // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead
- SceneTree::get_singleton()->disconnect("idle_frame", this, "_make_api_solutions_if_needed");
-
- _make_api_solutions_if_needed_impl();
-
- recursion_guard = false;
- }
-}
-
-void GodotSharpEditor::_make_api_solutions_if_needed_impl() {
- // If the project has a solution and C# project make sure the API assemblies are present and up to date
- String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
-
- if (!FileAccess::exists(res_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll")) ||
- GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) {
- if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
- return;
- }
-
- if (!FileAccess::exists(res_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll")) ||
- GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) {
- if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
- return; // Redundant? I don't think so
- }
-}
-
-void GodotSharpEditor::_remove_create_sln_menu_option() {
-
- menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN));
-
- bottom_panel_btn->show();
-}
-
-void GodotSharpEditor::_show_about_dialog() {
-
- bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start");
- about_dialog_checkbox->set_pressed(show_on_start);
- about_dialog->popup_centered_minsize();
-}
-
-void GodotSharpEditor::_toggle_about_dialog_on_start(bool p_enabled) {
-
- bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start");
- if (show_on_start != p_enabled) {
- EditorSettings::get_singleton()->set_setting("mono/editor/show_info_on_start", p_enabled);
- }
-}
-
-void GodotSharpEditor::_build_solution_pressed() {
-
- if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) {
- if (!_create_project_solution())
- return; // Failed to create solution
- }
-
- MonoBottomPanel::get_singleton()->call("_build_project_pressed");
-}
-
-void GodotSharpEditor::_menu_option_pressed(int p_id) {
-
- switch (p_id) {
- case MENU_CREATE_SLN: {
-
- _create_project_solution();
- } break;
- case MENU_ABOUT_CSHARP: {
-
- _show_about_dialog();
- } break;
- default:
- ERR_FAIL();
- }
-}
-
-void GodotSharpEditor::_notification(int p_notification) {
-
- switch (p_notification) {
-
- case NOTIFICATION_READY: {
-
- bool show_info_dialog = EDITOR_GET("mono/editor/show_info_on_start");
- if (show_info_dialog) {
- about_dialog->set_exclusive(true);
- _show_about_dialog();
- // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive then.
- about_dialog->set_exclusive(false);
- }
- }
- }
-}
-
-void GodotSharpEditor::_bind_methods() {
-
- ClassDB::bind_method(D_METHOD("_build_solution_pressed"), &GodotSharpEditor::_build_solution_pressed);
- ClassDB::bind_method(D_METHOD("_create_project_solution"), &GodotSharpEditor::_create_project_solution);
- ClassDB::bind_method(D_METHOD("_make_api_solutions_if_needed"), &GodotSharpEditor::_make_api_solutions_if_needed);
- ClassDB::bind_method(D_METHOD("_remove_create_sln_menu_option"), &GodotSharpEditor::_remove_create_sln_menu_option);
- ClassDB::bind_method(D_METHOD("_toggle_about_dialog_on_start"), &GodotSharpEditor::_toggle_about_dialog_on_start);
- ClassDB::bind_method(D_METHOD("_menu_option_pressed", "id"), &GodotSharpEditor::_menu_option_pressed);
-}
-
-MonoBoolean godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled(MonoString *p_bundle_id) {
-#ifdef OSX_ENABLED
- return (MonoBoolean)osx_is_app_bundle_installed(GDMonoMarshal::mono_string_to_godot(p_bundle_id));
-#else
- (void)p_bundle_id; // UNUSED
- ERR_FAIL_V(false);
-#endif
-}
-
-MonoString *godot_icall_Utils_OS_GetPlatformName() {
- return GDMonoMarshal::mono_string_from_godot(OS::get_singleton()->get_name());
-}
-
-void GodotSharpEditor::register_internal_calls() {
-
- static bool registered = false;
- ERR_FAIL_COND(registered);
- registered = true;
-
- mono_add_internal_call("GodotSharpTools.Editor.MonoDevelopInstance::IsApplicationBundleInstalled", (void *)godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled);
- mono_add_internal_call("GodotSharpTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
-
- GodotSharpBuilds::register_internal_calls();
- GodotSharpExport::register_internal_calls();
-}
-
-void GodotSharpEditor::show_error_dialog(const String &p_message, const String &p_title) {
-
- error_dialog->set_title(p_title);
- error_dialog->set_text(p_message);
- error_dialog->popup_centered_minsize();
-}
-
-Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) {
-
- ExternalEditor editor = ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor")));
-
- switch (editor) {
- case EDITOR_VSCODE: {
- static String vscode_path;
-
- if (vscode_path.empty() || !FileAccess::exists(vscode_path)) {
- // Try to search it again if it wasn't found last time or if it was removed from its location
- bool found = false;
-
- // TODO: Use initializer lists once C++11 is allowed
-
- static Vector<String> vscode_names;
- if (vscode_names.empty()) {
- vscode_names.push_back("code");
- vscode_names.push_back("code-oss");
- vscode_names.push_back("vscode");
- vscode_names.push_back("vscode-oss");
- vscode_names.push_back("visual-studio-code");
- vscode_names.push_back("visual-studio-code-oss");
- }
- for (int i = 0; i < vscode_names.size(); i++) {
- vscode_path = path_which(vscode_names[i]);
- if (!vscode_path.empty()) {
- found = true;
- break;
- }
- }
-
- if (!found)
- vscode_path.clear(); // Not found, clear so next time the empty() check is enough
- }
-
- List<String> args;
-
-#ifdef OSX_ENABLED
- // The package path is '/Applications/Visual Studio Code.app'
- static const String vscode_bundle_id = "com.microsoft.VSCode";
- static bool osx_app_bundle_installed = osx_is_app_bundle_installed(vscode_bundle_id);
-
- if (osx_app_bundle_installed) {
- args.push_back("-b");
- args.push_back(vscode_bundle_id);
-
- // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
- // editing our folder. It's better to ask for a new window and let VSCode do the window management.
- args.push_back("-n");
-
- // The open process must wait until the application finishes (which is instant in VSCode's case)
- args.push_back("--wait-apps");
-
- args.push_back("--args");
- }
-#endif
-
- args.push_back(ProjectSettings::get_singleton()->get_resource_path());
-
- String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path());
-
- if (p_line >= 0) {
- args.push_back("-g");
- args.push_back(script_path + ":" + itos(p_line + 1) + ":" + itos(p_col));
- } else {
- args.push_back(script_path);
- }
-
-#ifdef OSX_ENABLED
- ERR_EXPLAIN("Cannot find code editor: VSCode");
- ERR_FAIL_COND_V(!osx_app_bundle_installed && vscode_path.empty(), ERR_FILE_NOT_FOUND);
-
- String command = osx_app_bundle_installed ? "/usr/bin/open" : vscode_path;
-#else
- ERR_EXPLAIN("Cannot find code editor: VSCode");
- ERR_FAIL_COND_V(vscode_path.empty(), ERR_FILE_NOT_FOUND);
-
- String command = vscode_path;
-#endif
-
- Error err = OS::get_singleton()->execute(command, args, false);
-
- if (err != OK) {
- ERR_PRINT("Error when trying to execute code editor: VSCode");
- return err;
- }
- } break;
-#ifdef OSX_ENABLED
- case EDITOR_VISUALSTUDIO_MAC:
- // [[fallthrough]];
-#endif
- case EDITOR_MONODEVELOP: {
-#ifdef OSX_ENABLED
- bool is_visualstudio = editor == EDITOR_VISUALSTUDIO_MAC;
-
- MonoDevelopInstance **instance = is_visualstudio ?
- &visualstudio_mac_instance :
- &monodevelop_instance;
-
- MonoDevelopInstance::EditorId editor_id = is_visualstudio ?
- MonoDevelopInstance::VISUALSTUDIO_FOR_MAC :
- MonoDevelopInstance::MONODEVELOP;
-#else
- MonoDevelopInstance **instance = &monodevelop_instance;
- MonoDevelopInstance::EditorId editor_id = MonoDevelopInstance::MONODEVELOP;
-#endif
-
- if (!*instance)
- *instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path(), editor_id));
-
- String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path());
-
- if (p_line >= 0) {
- script_path += ";" + itos(p_line + 1) + ";" + itos(p_col);
- }
-
- (*instance)->execute(script_path);
- } break;
- default:
- return ERR_UNAVAILABLE;
- }
-
- return OK;
-}
-
-bool GodotSharpEditor::overrides_external_editor() {
-
- return ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))) != EDITOR_NONE;
-}
-
-GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) {
-
- singleton = this;
-
- monodevelop_instance = NULL;
-#ifdef OSX_ENABLED
- visualstudio_mac_instance = NULL;
-#endif
-
- editor = p_editor;
-
- error_dialog = memnew(AcceptDialog);
- editor->get_gui_base()->add_child(error_dialog);
-
- bottom_panel_btn = editor->add_bottom_panel_item(TTR("Mono"), memnew(MonoBottomPanel(editor)));
-
- godotsharp_builds = memnew(GodotSharpBuilds);
-
- editor->add_child(memnew(MonoReloadNode));
-
- menu_popup = memnew(PopupMenu);
- menu_popup->hide();
- menu_popup->set_as_toplevel(true);
- menu_popup->set_pass_on_modal_close_click(false);
-
- editor->add_tool_submenu_item("Mono", menu_popup);
-
- // TODO: Remove or edit this info dialog once Mono support is no longer in alpha
- {
- menu_popup->add_item(TTR("About C# support"), MENU_ABOUT_CSHARP);
- about_dialog = memnew(AcceptDialog);
- editor->get_gui_base()->add_child(about_dialog);
- about_dialog->set_title("Important: C# support is not feature-complete");
-
- // We don't use set_text() as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
- // we'll add. Instead we add containers and a new autowrapped Label inside.
-
- // Main VBoxContainer (icon + label on top, checkbox at bottom)
- VBoxContainer *about_vbc = memnew(VBoxContainer);
- about_dialog->add_child(about_vbc);
-
- // HBoxContainer for icon + label
- HBoxContainer *about_hbc = memnew(HBoxContainer);
- about_vbc->add_child(about_hbc);
-
- TextureRect *about_icon = memnew(TextureRect);
- about_hbc->add_child(about_icon);
- Ref<Texture> about_icon_tex = about_icon->get_icon("NodeWarning", "EditorIcons");
- about_icon->set_texture(about_icon_tex);
-
- Label *about_label = memnew(Label);
- about_hbc->add_child(about_label);
- about_label->set_custom_minimum_size(Size2(600, 150) * EDSCALE);
- about_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- about_label->set_autowrap(true);
- String about_text =
- String("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 and Windows, but not yet to mobile or web platforms. " +
- "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" +
- " https://github.com/godotengine/godot/issues\n\n" +
- "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!";
- about_label->set_text(about_text);
-
- EDITOR_DEF("mono/editor/show_info_on_start", true);
-
- // CheckBox in main container
- about_dialog_checkbox = memnew(CheckBox);
- about_vbc->add_child(about_dialog_checkbox);
- about_dialog_checkbox->set_text("Show this warning when starting the editor");
- about_dialog_checkbox->connect("toggled", this, "_toggle_about_dialog_on_start");
- }
-
- String sln_path = GodotSharpDirs::get_project_sln_path();
- String csproj_path = GodotSharpDirs::get_project_csproj_path();
-
- if (FileAccess::exists(sln_path) && FileAccess::exists(csproj_path)) {
- // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized.
- call_deferred("_make_api_solutions_if_needed");
- } else {
- bottom_panel_btn->hide();
- menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN);
- }
-
- menu_popup->connect("id_pressed", this, "_menu_option_pressed");
-
- ToolButton *build_button = memnew(ToolButton);
- build_button->set_text("Build");
- build_button->set_tooltip("Build solution");
- build_button->set_focus_mode(Control::FOCUS_NONE);
- build_button->connect("pressed", this, "_build_solution_pressed");
- editor->get_menu_hb()->add_child(build_button);
-
- // External editor settings
- EditorSettings *ed_settings = EditorSettings::get_singleton();
- EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE);
-
- String settings_hint_str = "Disabled";
-
-#if defined(WINDOWS_ENABLED)
- settings_hint_str += ",MonoDevelop,Visual Studio Code";
-#elif defined(OSX_ENABLED)
- settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code";
-#elif defined(UNIX_ENABLED)
- settings_hint_str += ",MonoDevelop,Visual Studio Code";
-#endif
-
- ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, settings_hint_str));
-
- // Export plugin
- Ref<GodotSharpExport> godotsharp_export;
- godotsharp_export.instance();
- EditorExport::get_singleton()->add_export_plugin(godotsharp_export);
-}
-
-GodotSharpEditor::~GodotSharpEditor() {
-
- singleton = NULL;
-
- memdelete(godotsharp_builds);
-
- if (monodevelop_instance) {
- memdelete(monodevelop_instance);
- monodevelop_instance = NULL;
- }
-}
-
-MonoReloadNode *MonoReloadNode::singleton = NULL;
-
-void MonoReloadNode::_reload_timer_timeout() {
-
- if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
- CSharpLanguage::get_singleton()->reload_assemblies(false);
- }
-}
-
-void MonoReloadNode::restart_reload_timer() {
-
- reload_timer->stop();
- reload_timer->start();
-}
-
-void MonoReloadNode::_bind_methods() {
-
- ClassDB::bind_method(D_METHOD("_reload_timer_timeout"), &MonoReloadNode::_reload_timer_timeout);
-}
-
-void MonoReloadNode::_notification(int p_what) {
- switch (p_what) {
- case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
- restart_reload_timer();
- if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
- CSharpLanguage::get_singleton()->reload_assemblies(false);
- }
- } break;
- default: {
- } break;
- };
-}
-
-MonoReloadNode::MonoReloadNode() {
-
- singleton = this;
-
- reload_timer = memnew(Timer);
- add_child(reload_timer);
- reload_timer->set_one_shot(false);
- reload_timer->set_wait_time(EDITOR_DEF("mono/assembly_watch_interval_sec", 0.5));
- reload_timer->connect("timeout", this, "_reload_timer_timeout");
- reload_timer->start();
-}
-
-MonoReloadNode::~MonoReloadNode() {
-
- singleton = NULL;
-}
diff --git a/modules/mono/editor/godotsharp_editor.h b/modules/mono/editor/godotsharp_editor.h
deleted file mode 100644
index d9523c384c..0000000000
--- a/modules/mono/editor/godotsharp_editor.h
+++ /dev/null
@@ -1,134 +0,0 @@
-/*************************************************************************/
-/* godotsharp_editor.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 GODOTSHARP_EDITOR_H
-#define GODOTSHARP_EDITOR_H
-
-#include "godotsharp_builds.h"
-#include "monodevelop_instance.h"
-
-class GodotSharpEditor : public Node {
- GDCLASS(GodotSharpEditor, Object)
-
- EditorNode *editor;
-
- MenuButton *menu_button;
- PopupMenu *menu_popup;
-
- AcceptDialog *error_dialog;
- AcceptDialog *about_dialog;
- CheckBox *about_dialog_checkbox;
-
- ToolButton *bottom_panel_btn;
-
- GodotSharpBuilds *godotsharp_builds;
-
- MonoDevelopInstance *monodevelop_instance;
-#ifdef OSX_ENABLED
- MonoDevelopInstance *visualstudio_mac_instance;
-#endif
-
- bool _create_project_solution();
- void _make_api_solutions_if_needed();
- void _make_api_solutions_if_needed_impl();
-
- void _remove_create_sln_menu_option();
- void _show_about_dialog();
- void _toggle_about_dialog_on_start(bool p_enabled);
-
- void _menu_option_pressed(int p_id);
-
- void _build_solution_pressed();
-
- static GodotSharpEditor *singleton;
-
-protected:
- void _notification(int p_notification);
- static void _bind_methods();
-
-public:
- enum MenuOptions {
- MENU_CREATE_SLN,
- MENU_ABOUT_CSHARP,
- };
-
- enum ExternalEditor {
- EDITOR_NONE,
-#if defined(WINDOWS_ENABLED)
- //EDITOR_VISUALSTUDIO, // TODO
- EDITOR_MONODEVELOP,
- EDITOR_VSCODE
-#elif defined(OSX_ENABLED)
- EDITOR_VISUALSTUDIO_MAC,
- EDITOR_MONODEVELOP,
- EDITOR_VSCODE
-#elif defined(UNIX_ENABLED)
- EDITOR_MONODEVELOP,
- EDITOR_VSCODE
-#endif
- };
-
- _FORCE_INLINE_ static GodotSharpEditor *get_singleton() { return singleton; }
-
- static void register_internal_calls();
-
- void show_error_dialog(const String &p_message, const String &p_title = "Error");
-
- Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col);
- bool overrides_external_editor();
-
- GodotSharpEditor(EditorNode *p_editor);
- ~GodotSharpEditor();
-};
-
-class MonoReloadNode : public Node {
- GDCLASS(MonoReloadNode, Node)
-
- Timer *reload_timer;
-
- void _reload_timer_timeout();
-
- static MonoReloadNode *singleton;
-
-protected:
- static void _bind_methods();
-
- void _notification(int p_what);
-
-public:
- _FORCE_INLINE_ static MonoReloadNode *get_singleton() { return singleton; }
-
- void restart_reload_timer();
-
- MonoReloadNode();
- ~MonoReloadNode();
-};
-
-#endif // GODOTSHARP_EDITOR_H
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index ee5fed1a0c..020bb70a08 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -30,164 +30,28 @@
#include "godotsharp_export.h"
-#include "core/version.h"
-
-#include "../csharp_script.h"
-#include "../godotsharp_defs.h"
-#include "../godotsharp_dirs.h"
-#include "../mono_gd/gd_mono_class.h"
-#include "../mono_gd/gd_mono_marshal.h"
-#include "csharp_project.h"
-#include "godotsharp_builds.h"
-
-static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() {
- String current_version = VERSION_FULL_CONFIG;
- String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version);
- return GDMonoMarshal::mono_string_from_godot(ProjectSettings::get_singleton()->globalize_path(templates_dir));
-}
-
-static MonoString *godot_icall_GodotSharpExport_GetDataDirName() {
- String appname = ProjectSettings::get_singleton()->get("application/config/name");
- String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
- return GDMonoMarshal::mono_string_from_godot("data_" + appname_safe);
-}
+#include <mono/metadata/image.h>
-void GodotSharpExport::register_internal_calls() {
- static bool registered = false;
- ERR_FAIL_COND(registered);
- registered = true;
+#include "../mono_gd/gd_mono.h"
+#include "../mono_gd/gd_mono_assembly.h"
- mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetTemplatesDir", (void *)godot_icall_GodotSharpExport_GetTemplatesDir);
- mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetDataDirName", (void *)godot_icall_GodotSharpExport_GetDataDirName);
-}
+String get_assemblyref_name(MonoImage *p_image, int index) {
+ const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
-void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &) {
+ uint32_t cols[MONO_ASSEMBLYREF_SIZE];
- if (p_type != CSharpLanguage::get_singleton()->get_type())
- return;
+ mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
- ERR_FAIL_COND(p_path.get_extension() != CSharpLanguage::get_singleton()->get_extension());
-
- // TODO what if the source file is not part of the game's C# project
-
- if (!GLOBAL_GET("mono/export/include_scripts_content")) {
- // We don't want to include the source code on exported games
- add_file(p_path, Vector<uint8_t>(), false);
- skip();
- }
+ return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
}
-void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) {
-
- // TODO right now there is no way to stop the export process with an error
-
- ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized());
- ERR_FAIL_NULL(TOOLS_DOMAIN);
- ERR_FAIL_NULL(GDMono::get_singleton()->get_editor_tools_assembly());
-
- if (FileAccess::exists(GodotSharpDirs::get_project_sln_path())) {
- String build_config = p_debug ? "Debug" : "Release";
-
- String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release"));
- Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
- ERR_FAIL_COND(metadata_err != OK);
-
- ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path));
-
- ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config));
-
- // Add dependency assemblies
-
- Map<String, String> dependencies;
-
- String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name");
- if (project_dll_name.empty()) {
- project_dll_name = "UnnamedProject";
- }
-
- String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config);
- String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name + ".dll");
- dependencies.insert(project_dll_name, project_dll_src_path);
-
- {
- MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
- ERR_FAIL_NULL(export_domain);
- _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
-
- _GDMONO_SCOPE_DOMAIN_(export_domain);
-
- GDMonoAssembly *scripts_assembly = NULL;
- bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name,
- project_dll_src_path, &scripts_assembly, /* refonly: */ true);
-
- ERR_EXPLAIN("Cannot load refonly assembly: " + project_dll_name);
- ERR_FAIL_COND(!load_success);
-
- Vector<String> search_dirs;
- GDMonoAssembly::fill_search_dirs(search_dirs, build_config);
- Error depend_error = _get_assembly_dependencies(scripts_assembly, search_dirs, dependencies);
- ERR_FAIL_COND(depend_error != OK);
- }
-
- for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) {
- String depend_src_path = E->value();
- String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file());
- ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path));
- }
- }
-
- // Mono specific export template extras (data dir)
-
- GDMonoClass *export_class = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "GodotSharpExport");
- ERR_FAIL_NULL(export_class);
- GDMonoMethod *export_begin_method = export_class->get_method("_ExportBegin", 4);
- ERR_FAIL_NULL(export_begin_method);
-
- MonoArray *features = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_features.size());
- int i = 0;
- for (const Set<String>::Element *E = p_features.front(); E; E = E->next()) {
- MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get());
- mono_array_set(features, MonoString *, i, boxed);
- i++;
- }
-
- MonoBoolean debug = p_debug;
- MonoString *path = GDMonoMarshal::mono_string_from_godot(p_path);
- uint32_t flags = p_flags;
- void *args[4] = { features, &debug, path, &flags };
- MonoException *exc = NULL;
- export_begin_method->invoke_raw(NULL, args, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL();
- }
-}
-
-bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) {
-
- FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ);
- ERR_FAIL_COND_V(!f, false);
-
- Vector<uint8_t> data;
- data.resize(f->get_len());
- f->get_buffer(data.ptrw(), data.size());
-
- add_file(p_dst_path, data, p_remap);
-
- return true;
-}
-
-Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies) {
-
+Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
- MonoAssemblyName *ref_aname = aname_prealloc;
- mono_assembly_get_assemblyref(image, i, ref_aname);
- String ref_name = mono_assembly_name_get_name(ref_aname);
+ String ref_name = get_assemblyref_name(image, i);
- if (r_dependencies.find(ref_name))
+ if (r_dependencies.has(ref_name))
continue;
GDMonoAssembly *ref_assembly = NULL;
@@ -226,24 +90,34 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c
ERR_FAIL_V(ERR_CANT_RESOLVE);
}
- r_dependencies.insert(ref_name, ref_assembly->get_path());
+ r_dependencies[ref_name] = ref_assembly->get_path();
- Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
- if (err != OK)
- return err;
+ Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
+ if (err != OK) {
+ ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name);
+ ERR_FAIL_V(err);
+ }
}
return OK;
}
-GodotSharpExport::GodotSharpExport() {
- // MonoAssemblyName is an incomplete type (internal to mono), so we can't allocate it ourselves.
- // There isn't any api to allocate an empty one either, so we need to do it this way.
- aname_prealloc = mono_assembly_name_new("whatever");
- mono_assembly_name_free(aname_prealloc); // "it does not frees the object itself, only the name members" (typo included)
-}
+Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) {
+ MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
+ ERR_FAIL_NULL_V(export_domain, FAILED);
+ _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
+
+ _GDMONO_SCOPE_DOMAIN_(export_domain);
+
+ GDMonoAssembly *scripts_assembly = NULL;
+ bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name,
+ p_project_dll_src_path, &scripts_assembly, /* refonly: */ true);
+
+ ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name);
+ ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE);
+
+ Vector<String> search_dirs;
+ GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir);
-GodotSharpExport::~GodotSharpExport() {
- if (aname_prealloc)
- mono_free(aname_prealloc);
+ return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies);
}
diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h
index 4dc8ea75d5..8d121a6bc3 100644
--- a/modules/mono/editor/godotsharp_export.h
+++ b/modules/mono/editor/godotsharp_export.h
@@ -31,29 +31,19 @@
#ifndef GODOTSHARP_EXPORT_H
#define GODOTSHARP_EXPORT_H
-#include <mono/metadata/image.h>
-
-#include "editor/editor_export.h"
+#include "core/dictionary.h"
+#include "core/error_list.h"
+#include "core/ustring.h"
#include "../mono_gd/gd_mono_header.h"
-class GodotSharpExport : public EditorExportPlugin {
-
- MonoAssemblyName *aname_prealloc;
-
- bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false);
-
- Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies);
-
-protected:
- virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features);
- virtual void _export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags);
+namespace GodotSharpExport {
-public:
- static void register_internal_calls();
+Error get_exported_assembly_dependencies(const String &p_project_dll_name,
+ const String &p_project_dll_src_path, const String &p_build_config,
+ const String &p_custom_lib_dir, Dictionary &r_dependencies);
+Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
- GodotSharpExport();
- ~GodotSharpExport();
-};
+} // namespace GodotSharpExport
#endif // GODOTSHARP_EXPORT_H
diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp
deleted file mode 100644
index 21ce9ca5c4..0000000000
--- a/modules/mono/editor/mono_bottom_panel.cpp
+++ /dev/null
@@ -1,525 +0,0 @@
-/*************************************************************************/
-/* mono_bottom_panel.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 "mono_bottom_panel.h"
-
-#include "editor/plugins/script_editor_plugin.h"
-#include "editor/script_editor_debugger.h"
-
-#include "../csharp_script.h"
-#include "../godotsharp_dirs.h"
-#include "csharp_project.h"
-#include "godotsharp_editor.h"
-
-MonoBottomPanel *MonoBottomPanel::singleton = NULL;
-
-void MonoBottomPanel::_update_build_tabs_list() {
-
- build_tabs_list->clear();
-
- int current_tab = build_tabs->get_current_tab();
-
- bool no_current_tab = current_tab < 0 || current_tab >= build_tabs->get_tab_count();
-
- for (int i = 0; i < build_tabs->get_child_count(); i++) {
-
- MonoBuildTab *tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(i));
-
- if (tab) {
- String item_name = tab->build_info.solution.get_file().get_basename();
- item_name += " [" + tab->build_info.configuration + "]";
-
- build_tabs_list->add_item(item_name, tab->get_icon_texture());
-
- String item_tooltip = "Solution: " + tab->build_info.solution;
- item_tooltip += "\nConfiguration: " + tab->build_info.configuration;
- item_tooltip += "\nStatus: ";
-
- if (tab->build_exited) {
- item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored";
- } else {
- item_tooltip += "Running";
- }
-
- if (!tab->build_exited || tab->build_result == MonoBuildTab::RESULT_ERROR) {
- item_tooltip += "\nErrors: " + itos(tab->error_count);
- }
-
- item_tooltip += "\nWarnings: " + itos(tab->warning_count);
-
- build_tabs_list->set_item_tooltip(i, item_tooltip);
-
- if (no_current_tab || current_tab == i) {
- build_tabs_list->select(i);
- _build_tabs_item_selected(i);
- }
- }
- }
-}
-
-void MonoBottomPanel::add_build_tab(MonoBuildTab *p_build_tab) {
-
- build_tabs->add_child(p_build_tab);
- raise_build_tab(p_build_tab);
-}
-
-void MonoBottomPanel::raise_build_tab(MonoBuildTab *p_build_tab) {
-
- ERR_FAIL_COND(p_build_tab->get_parent() != build_tabs);
- build_tabs->move_child(p_build_tab, 0);
- _update_build_tabs_list();
-}
-
-void MonoBottomPanel::show_build_tab() {
-
- for (int i = 0; i < panel_tabs->get_tab_count(); i++) {
- if (panel_tabs->get_tab_control(i) == panel_builds_tab) {
- panel_tabs->set_current_tab(i);
- editor->make_bottom_panel_item_visible(this);
- return;
- }
- }
-
- ERR_PRINT("Builds tab not found");
-}
-
-void MonoBottomPanel::_build_tabs_item_selected(int p_idx) {
-
- ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count());
-
- build_tabs->set_current_tab(p_idx);
- if (!build_tabs->is_visible())
- build_tabs->set_visible(true);
-
- warnings_btn->set_visible(true);
- errors_btn->set_visible(true);
- view_log_btn->set_visible(true);
-}
-
-void MonoBottomPanel::_build_tabs_nothing_selected() {
-
- if (build_tabs->get_tab_count() != 0) { // just in case
- build_tabs->set_visible(false);
-
- // This callback is called when clicking on the empty space of the list.
- // ItemList won't deselect the items automatically, so we must do it ourselves.
- build_tabs_list->unselect_all();
- }
-
- warnings_btn->set_visible(false);
- errors_btn->set_visible(false);
- view_log_btn->set_visible(false);
-}
-
-void MonoBottomPanel::_warnings_toggled(bool p_pressed) {
-
- int current_tab = build_tabs->get_current_tab();
- ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count());
- MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab));
- build_tab->warnings_visible = p_pressed;
- build_tab->_update_issues_list();
-}
-
-void MonoBottomPanel::_errors_toggled(bool p_pressed) {
-
- int current_tab = build_tabs->get_current_tab();
- ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count());
- MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab));
- build_tab->errors_visible = p_pressed;
- build_tab->_update_issues_list();
-}
-
-void MonoBottomPanel::_build_project_pressed() {
-
- if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
- return; // No solution to build
-
- String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
- String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player");
-
- Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor);
- ERR_FAIL_COND(metadata_err != OK);
-
- if (FileAccess::exists(scripts_metadata_path_editor)) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player);
-
- ERR_EXPLAIN("Failed to copy scripts metadata file");
- ERR_FAIL_COND(copy_err != OK);
- }
-
- bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
-
- if (build_success) {
- // Notify running game for hot-reload
- ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
-
- // Hot-reload in the editor
- MonoReloadNode::get_singleton()->restart_reload_timer();
-
- if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
- CSharpLanguage::get_singleton()->reload_assemblies(false);
- }
- }
-}
-
-void MonoBottomPanel::_view_log_pressed() {
-
- if (build_tabs_list->is_anything_selected()) {
- Vector<int> selected_items = build_tabs_list->get_selected_items();
- CRASH_COND(selected_items.size() != 1);
- int selected_item = selected_items[0];
-
- MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_tab_control(selected_item));
- ERR_FAIL_NULL(build_tab);
-
- String log_dirpath = build_tab->get_build_info().get_log_dirpath();
-
- OS::get_singleton()->shell_open(log_dirpath.plus_file(GodotSharpBuilds::get_msbuild_log_filename()));
- }
-}
-
-void MonoBottomPanel::_notification(int p_what) {
-
- switch (p_what) {
-
- case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles"));
- panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles"));
- panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles"));
- } break;
- }
-}
-
-void MonoBottomPanel::_bind_methods() {
-
- ClassDB::bind_method(D_METHOD("_build_project_pressed"), &MonoBottomPanel::_build_project_pressed);
- ClassDB::bind_method(D_METHOD("_view_log_pressed"), &MonoBottomPanel::_view_log_pressed);
- ClassDB::bind_method(D_METHOD("_warnings_toggled", "pressed"), &MonoBottomPanel::_warnings_toggled);
- ClassDB::bind_method(D_METHOD("_errors_toggled", "pressed"), &MonoBottomPanel::_errors_toggled);
- ClassDB::bind_method(D_METHOD("_build_tabs_item_selected", "idx"), &MonoBottomPanel::_build_tabs_item_selected);
- ClassDB::bind_method(D_METHOD("_build_tabs_nothing_selected"), &MonoBottomPanel::_build_tabs_nothing_selected);
-}
-
-MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) {
-
- singleton = this;
-
- editor = p_editor;
-
- set_v_size_flags(SIZE_EXPAND_FILL);
- set_anchors_and_margins_preset(Control::PRESET_WIDE);
-
- panel_tabs = memnew(TabContainer);
- panel_tabs->set_tab_align(TabContainer::ALIGN_LEFT);
- panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles"));
- panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles"));
- panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles"));
- panel_tabs->set_custom_minimum_size(Size2(0, 228) * EDSCALE);
- panel_tabs->set_v_size_flags(SIZE_EXPAND_FILL);
- add_child(panel_tabs);
-
- { // Builds
- panel_builds_tab = memnew(VBoxContainer);
- panel_builds_tab->set_name(TTR("Builds"));
- panel_builds_tab->set_h_size_flags(SIZE_EXPAND_FILL);
- panel_tabs->add_child(panel_builds_tab);
-
- HBoxContainer *toolbar_hbc = memnew(HBoxContainer);
- toolbar_hbc->set_h_size_flags(SIZE_EXPAND_FILL);
- panel_builds_tab->add_child(toolbar_hbc);
-
- Button *build_project_btn = memnew(Button);
- build_project_btn->set_text(TTR("Build Project"));
- build_project_btn->set_focus_mode(FOCUS_NONE);
- build_project_btn->connect("pressed", this, "_build_project_pressed");
- toolbar_hbc->add_child(build_project_btn);
-
- toolbar_hbc->add_spacer();
-
- warnings_btn = memnew(ToolButton);
- warnings_btn->set_text(TTR("Warnings"));
- warnings_btn->set_toggle_mode(true);
- warnings_btn->set_pressed(true);
- warnings_btn->set_visible(false);
- warnings_btn->set_focus_mode(FOCUS_NONE);
- warnings_btn->connect("toggled", this, "_warnings_toggled");
- toolbar_hbc->add_child(warnings_btn);
-
- errors_btn = memnew(ToolButton);
- errors_btn->set_text(TTR("Errors"));
- errors_btn->set_toggle_mode(true);
- errors_btn->set_pressed(true);
- errors_btn->set_visible(false);
- errors_btn->set_focus_mode(FOCUS_NONE);
- errors_btn->connect("toggled", this, "_errors_toggled");
- toolbar_hbc->add_child(errors_btn);
-
- toolbar_hbc->add_spacer();
-
- view_log_btn = memnew(Button);
- view_log_btn->set_text(TTR("View log"));
- view_log_btn->set_focus_mode(FOCUS_NONE);
- view_log_btn->set_visible(false);
- view_log_btn->connect("pressed", this, "_view_log_pressed");
- toolbar_hbc->add_child(view_log_btn);
-
- HSplitContainer *hsc = memnew(HSplitContainer);
- hsc->set_h_size_flags(SIZE_EXPAND_FILL);
- hsc->set_v_size_flags(SIZE_EXPAND_FILL);
- panel_builds_tab->add_child(hsc);
-
- build_tabs_list = memnew(ItemList);
- build_tabs_list->set_h_size_flags(SIZE_EXPAND_FILL);
- build_tabs_list->connect("item_selected", this, "_build_tabs_item_selected");
- build_tabs_list->connect("nothing_selected", this, "_build_tabs_nothing_selected");
- hsc->add_child(build_tabs_list);
-
- build_tabs = memnew(TabContainer);
- build_tabs->set_tab_align(TabContainer::ALIGN_LEFT);
- build_tabs->set_h_size_flags(SIZE_EXPAND_FILL);
- build_tabs->set_tabs_visible(false);
- hsc->add_child(build_tabs);
- }
-}
-
-MonoBottomPanel::~MonoBottomPanel() {
-
- singleton = NULL;
-}
-
-void MonoBuildTab::_load_issues_from_file(const String &p_csv_file) {
-
- FileAccessRef f = FileAccess::open(p_csv_file, FileAccess::READ);
-
- if (!f)
- return;
-
- while (!f->eof_reached()) {
- Vector<String> csv_line = f->get_csv_line();
-
- if (csv_line.size() == 1 && csv_line[0].empty())
- return;
-
- ERR_CONTINUE(csv_line.size() != 7);
-
- BuildIssue issue;
- issue.warning = csv_line[0] == "warning";
- issue.file = csv_line[1];
- issue.line = csv_line[2].to_int();
- issue.column = csv_line[3].to_int();
- issue.code = csv_line[4];
- issue.message = csv_line[5];
- issue.project_file = csv_line[6];
-
- if (issue.warning)
- warning_count += 1;
- else
- error_count += 1;
-
- issues.push_back(issue);
- }
-}
-
-void MonoBuildTab::_update_issues_list() {
-
- issues_list->clear();
-
- Ref<Texture> warning_icon = get_icon("Warning", "EditorIcons");
- Ref<Texture> error_icon = get_icon("Error", "EditorIcons");
-
- for (int i = 0; i < issues.size(); i++) {
-
- const BuildIssue &issue = issues[i];
-
- if (!(issue.warning ? warnings_visible : errors_visible))
- continue;
-
- String tooltip;
- tooltip += String("Message: ") + issue.message;
-
- if (issue.code.length()) {
- tooltip += String("\nCode: ") + issue.code;
- }
-
- tooltip += String("\nType: ") + (issue.warning ? "warning" : "error");
-
- String text;
-
- if (issue.file.length()) {
- String sline = String::num_int64(issue.line);
- String scolumn = String::num_int64(issue.column);
-
- text += issue.file + "(";
- text += sline + ",";
- text += scolumn + "): ";
-
- tooltip += "\nFile: " + issue.file;
- tooltip += "\nLine: " + sline;
- tooltip += "\nColumn: " + scolumn;
- }
-
- if (issue.project_file.length()) {
- tooltip += "\nProject: " + issue.project_file;
- }
-
- text += issue.message;
-
- int line_break_idx = text.find("\n");
- issues_list->add_item(line_break_idx == -1 ? text : text.substr(0, line_break_idx),
- issue.warning ? warning_icon : error_icon);
- int index = issues_list->get_item_count() - 1;
- issues_list->set_item_tooltip(index, tooltip);
- issues_list->set_item_metadata(index, i);
- }
-}
-
-Ref<Texture> MonoBuildTab::get_icon_texture() const {
-
- if (build_exited) {
- if (build_result == RESULT_ERROR) {
- return get_icon("StatusError", "EditorIcons");
- } else {
- return get_icon("StatusSuccess", "EditorIcons");
- }
- } else {
- return get_icon("Stop", "EditorIcons");
- }
-}
-
-MonoBuildInfo MonoBuildTab::get_build_info() {
-
- return build_info;
-}
-
-void MonoBuildTab::on_build_start() {
-
- build_exited = false;
-
- issues.clear();
- warning_count = 0;
- error_count = 0;
- _update_issues_list();
-
- MonoBottomPanel::get_singleton()->raise_build_tab(this);
-}
-
-void MonoBuildTab::on_build_exit(BuildResult result) {
-
- build_exited = true;
- build_result = result;
-
- _load_issues_from_file(logs_dir.plus_file(GodotSharpBuilds::get_msbuild_issues_filename()));
- _update_issues_list();
-
- MonoBottomPanel::get_singleton()->raise_build_tab(this);
-}
-
-void MonoBuildTab::on_build_exec_failed(const String &p_cause) {
-
- build_exited = true;
- build_result = RESULT_ERROR;
-
- issues_list->clear();
-
- BuildIssue issue;
- issue.message = p_cause;
- issue.warning = false;
-
- error_count += 1;
- issues.push_back(issue);
-
- _update_issues_list();
-
- MonoBottomPanel::get_singleton()->raise_build_tab(this);
-}
-
-void MonoBuildTab::restart_build() {
-
- ERR_FAIL_COND(!build_exited);
- GodotSharpBuilds::get_singleton()->restart_build(this);
-}
-
-void MonoBuildTab::stop_build() {
-
- ERR_FAIL_COND(build_exited);
- GodotSharpBuilds::get_singleton()->stop_build(this);
-}
-
-void MonoBuildTab::_issue_activated(int p_idx) {
-
- ERR_FAIL_INDEX(p_idx, issues_list->get_item_count());
-
- // Get correct issue idx from issue list
- int issue_idx = this->issues_list->get_item_metadata(p_idx);
-
- ERR_FAIL_INDEX(issue_idx, issues.size());
-
- const BuildIssue &issue = issues[issue_idx];
-
- if (issue.project_file.empty() && issue.file.empty())
- return;
-
- String project_dir = issue.project_file.length() ? issue.project_file.get_base_dir() : build_info.solution.get_base_dir();
-
- String file = project_dir.simplify_path().plus_file(issue.file.simplify_path());
-
- if (!FileAccess::exists(file))
- return;
-
- file = ProjectSettings::get_singleton()->localize_path(file);
-
- if (file.begins_with("res://")) {
- Ref<Script> script = ResourceLoader::load(file, CSharpLanguage::get_singleton()->get_type());
-
- if (script.is_valid() && ScriptEditor::get_singleton()->edit(script, issue.line, issue.column)) {
- EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
- }
- }
-}
-
-void MonoBuildTab::_bind_methods() {
-
- ClassDB::bind_method("_issue_activated", &MonoBuildTab::_issue_activated);
-}
-
-MonoBuildTab::MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir) :
- build_exited(false),
- issues_list(memnew(ItemList)),
- error_count(0),
- warning_count(0),
- errors_visible(true),
- warnings_visible(true),
- logs_dir(p_logs_dir),
- build_info(p_build_info) {
- issues_list->set_v_size_flags(SIZE_EXPAND_FILL);
- issues_list->connect("item_activated", this, "_issue_activated");
- add_child(issues_list);
-}
diff --git a/modules/mono/editor/mono_bottom_panel.h b/modules/mono/editor/mono_bottom_panel.h
deleted file mode 100644
index 406e46f7ce..0000000000
--- a/modules/mono/editor/mono_bottom_panel.h
+++ /dev/null
@@ -1,150 +0,0 @@
-/*************************************************************************/
-/* mono_bottom_panel.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 MONO_BOTTOM_PANEL_H
-#define MONO_BOTTOM_PANEL_H
-
-#include "editor/editor_node.h"
-#include "scene/gui/control.h"
-
-#include "mono_build_info.h"
-
-class MonoBuildTab;
-
-class MonoBottomPanel : public VBoxContainer {
-
- GDCLASS(MonoBottomPanel, VBoxContainer)
-
- EditorNode *editor;
-
- TabContainer *panel_tabs;
-
- VBoxContainer *panel_builds_tab;
-
- ItemList *build_tabs_list;
- TabContainer *build_tabs;
-
- ToolButton *warnings_btn;
- ToolButton *errors_btn;
- Button *view_log_btn;
-
- void _update_build_tabs_list();
-
- void _build_tabs_item_selected(int p_idx);
- void _build_tabs_nothing_selected();
-
- void _warnings_toggled(bool p_pressed);
- void _errors_toggled(bool p_pressed);
-
- void _build_project_pressed();
- void _view_log_pressed();
-
- static MonoBottomPanel *singleton;
-
-protected:
- void _notification(int p_what);
-
- static void _bind_methods();
-
-public:
- _FORCE_INLINE_ static MonoBottomPanel *get_singleton() { return singleton; }
-
- void add_build_tab(MonoBuildTab *p_build_tab);
- void raise_build_tab(MonoBuildTab *p_build_tab);
-
- void show_build_tab();
-
- MonoBottomPanel(EditorNode *p_editor = NULL);
- ~MonoBottomPanel();
-};
-
-class MonoBuildTab : public VBoxContainer {
-
- GDCLASS(MonoBuildTab, VBoxContainer)
-
-public:
- enum BuildResult {
- RESULT_ERROR,
- RESULT_SUCCESS
- };
-
- struct BuildIssue {
- bool warning;
- String file;
- int line;
- int column;
- String code;
- String message;
- String project_file;
- };
-
-private:
- friend class MonoBottomPanel;
-
- bool build_exited;
- BuildResult build_result;
-
- Vector<BuildIssue> issues;
- ItemList *issues_list;
-
- int error_count;
- int warning_count;
-
- bool errors_visible;
- bool warnings_visible;
-
- String logs_dir;
-
- MonoBuildInfo build_info;
-
- void _load_issues_from_file(const String &p_csv_file);
- void _update_issues_list();
-
- void _issue_activated(int p_idx);
-
-protected:
- static void _bind_methods();
-
-public:
- Ref<Texture> get_icon_texture() const;
-
- MonoBuildInfo get_build_info();
-
- void on_build_start();
- void on_build_exit(BuildResult result);
- void on_build_exec_failed(const String &p_cause);
-
- void restart_build();
- void stop_build();
-
- MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir);
-};
-
-#endif // MONO_BOTTOM_PANEL_H
diff --git a/modules/mono/editor/mono_build_info.cpp b/modules/mono/editor/mono_build_info.cpp
deleted file mode 100644
index b386c06435..0000000000
--- a/modules/mono/editor/mono_build_info.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*************************************************************************/
-/* mono_build_info.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2019 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 "mono_build_info.h"
-
-#include "../godotsharp_dirs.h"
-#include "../mono_gd/gd_mono_utils.h"
-
-uint32_t MonoBuildInfo::Hasher::hash(const MonoBuildInfo &p_key) {
-
- uint32_t hash = 0;
-
- GDMonoUtils::hash_combine(hash, p_key.solution.hash());
- GDMonoUtils::hash_combine(hash, p_key.configuration.hash());
-
- return hash;
-}
-
-bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const {
-
- return p_b.solution == solution && p_b.configuration == configuration;
-}
-
-String MonoBuildInfo::get_log_dirpath() {
-
- return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration);
-}
-
-MonoBuildInfo::MonoBuildInfo() {}
-
-MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) {
-
- solution = p_solution;
- configuration = p_config;
-}
diff --git a/modules/mono/glue/Managed/.gitignore b/modules/mono/glue/Managed/.gitignore
new file mode 100644
index 0000000000..146421cac8
--- /dev/null
+++ b/modules/mono/glue/Managed/.gitignore
@@ -0,0 +1,2 @@
+# Generated Godot API solution folder
+Generated
diff --git a/modules/mono/glue/Managed/Files/AABB.cs b/modules/mono/glue/Managed/Files/AABB.cs
index 33b2b46712..a2ebbc0736 100644
--- a/modules/mono/glue/Managed/Files/AABB.cs
+++ b/modules/mono/glue/Managed/Files/AABB.cs
@@ -414,6 +414,21 @@ namespace Godot
_position = position;
_size = size;
}
+ public AABB(Vector3 position, real_t width, real_t height, real_t depth)
+ {
+ _position = position;
+ _size = new Vector3(width, height, depth);
+ }
+ public AABB(real_t x, real_t y, real_t z, Vector3 size)
+ {
+ _position = new Vector3(x, y, z);
+ _size = size;
+ }
+ 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);
+ _size = new Vector3(width, height, depth);
+ }
public static bool operator ==(AABB left, AABB right)
{
diff --git a/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs
index 2398e10135..1bf6d5199a 100644
--- a/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs
+++ b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs
@@ -2,27 +2,27 @@ using System;
namespace Godot
{
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class RemoteAttribute : Attribute {}
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class SyncAttribute : Attribute {}
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class MasterAttribute : Attribute {}
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class PuppetAttribute : Attribute {}
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class SlaveAttribute : Attribute {}
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class RemoteSyncAttribute : Attribute {}
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class MasterSyncAttribute : Attribute {}
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class PuppetSyncAttribute : Attribute {}
}
diff --git a/modules/mono/glue/Managed/Files/Basis.cs b/modules/mono/glue/Managed/Files/Basis.cs
index ac9576cebd..9cc31a0557 100644
--- a/modules/mono/glue/Managed/Files/Basis.cs
+++ b/modules/mono/glue/Managed/Files/Basis.cs
@@ -260,13 +260,13 @@ namespace Godot
Vector3 euler;
euler.z = 0.0f;
- real_t mxy = m.Row1[2];
+ real_t mzy = m.Row1[2];
- if (mxy < 1.0f)
+ if (mzy < 1.0f)
{
- if (mxy > -1.0f)
+ if (mzy > -1.0f)
{
- euler.x = Mathf.Asin(-mxy);
+ euler.x = Mathf.Asin(-mzy);
euler.y = Mathf.Atan2(m.Row0[2], m.Row2[2]);
euler.z = Mathf.Atan2(m.Row1[0], m.Row1[1]);
}
@@ -418,19 +418,11 @@ namespace Godot
public Basis Scaled(Vector3 scale)
{
- var m = this;
-
- m.Row0[0] *= scale.x;
- m.Row0[1] *= scale.x;
- m.Row0[2] *= scale.x;
- m.Row1[0] *= scale.y;
- m.Row1[1] *= scale.y;
- m.Row1[2] *= scale.y;
- m.Row2[0] *= scale.z;
- m.Row2[1] *= scale.z;
- m.Row2[2] *= scale.z;
-
- return m;
+ var b = this;
+ b.Row0 *= scale.x;
+ b.Row1 *= scale.y;
+ b.Row2 *= scale.z;
+ return b;
}
public real_t Tdotx(Vector3 with)
@@ -583,31 +575,29 @@ namespace Godot
public Basis(Vector3 axis, real_t phi)
{
- var axis_sq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z);
-
+ Vector3 axisSq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z);
real_t cosine = Mathf.Cos(phi);
+ Row0.x = axisSq.x + cosine * (1.0f - axisSq.x);
+ Row1.y = axisSq.y + cosine * (1.0f - axisSq.y);
+ Row2.z = axisSq.z + cosine * (1.0f - axisSq.z);
+
real_t sine = Mathf.Sin(phi);
+ real_t t = 1.0f - cosine;
- Row0 = new Vector3
- (
- axis_sq.x + cosine * (1.0f - axis_sq.x),
- axis.x * axis.y * (1.0f - cosine) - axis.z * sine,
- axis.z * axis.x * (1.0f - cosine) + axis.y * sine
- );
+ real_t xyzt = axis.x * axis.y * t;
+ real_t zyxs = axis.z * sine;
+ Row0.y = xyzt - zyxs;
+ Row1.x = xyzt + zyxs;
- Row1 = new Vector3
- (
- axis.x * axis.y * (1.0f - cosine) + axis.z * sine,
- axis_sq.y + cosine * (1.0f - axis_sq.y),
- axis.y * axis.z * (1.0f - cosine) - axis.x * sine
- );
+ xyzt = axis.x * axis.z * t;
+ zyxs = axis.y * sine;
+ Row0.z = xyzt + zyxs;
+ Row2.x = xyzt - zyxs;
- Row2 = new Vector3
- (
- axis.z * axis.x * (1.0f - cosine) - axis.y * sine,
- axis.y * axis.z * (1.0f - cosine) + axis.x * sine,
- axis_sq.z + cosine * (1.0f - axis_sq.z)
- );
+ xyzt = axis.y * axis.z * t;
+ zyxs = axis.x * sine;
+ Row1.z = xyzt - zyxs;
+ Row2.y = xyzt + zyxs;
}
public Basis(Vector3 column0, Vector3 column1, Vector3 column2)
@@ -622,11 +612,12 @@ namespace Godot
// We need to assign the struct fields here first so we can't do it that way...
}
- internal Basis(real_t xx, real_t xy, real_t xz, real_t yx, real_t yy, real_t yz, real_t zx, real_t zy, real_t zz)
+ // Arguments are named such that xy is equal to calling x.y
+ internal Basis(real_t xx, real_t yx, real_t zx, real_t xy, real_t yy, real_t zy, real_t xz, real_t yz, real_t zz)
{
- Row0 = new Vector3(xx, xy, xz);
- Row1 = new Vector3(yx, yy, yz);
- Row2 = new Vector3(zx, zy, zz);
+ Row0 = new Vector3(xx, yx, zx);
+ Row1 = new Vector3(xy, yy, zy);
+ Row2 = new Vector3(xz, yz, zz);
}
public static Basis operator *(Basis left, Basis right)
diff --git a/modules/mono/glue/Managed/Files/Color.cs b/modules/mono/glue/Managed/Files/Color.cs
index 88fa3323c2..84ff19fc54 100644
--- a/modules/mono/glue/Managed/Files/Color.cs
+++ b/modules/mono/glue/Managed/Files/Color.cs
@@ -168,7 +168,7 @@ namespace Godot
int max = Mathf.Max(color.r8, Mathf.Max(color.g8, color.b8));
int min = Mathf.Min(color.r8, Mathf.Min(color.g8, color.b8));
- float delta = max - min;
+ int delta = max - min;
if (delta == 0)
{
@@ -591,11 +591,11 @@ namespace Godot
public static bool operator <(Color left, Color right)
{
- if (left.r == right.r)
+ if (Mathf.IsEqualApprox(left.r, right.r))
{
- if (left.g == right.g)
+ if (Mathf.IsEqualApprox(left.g, right.g))
{
- if (left.b == right.b)
+ if (Mathf.IsEqualApprox(left.b, right.b))
return left.a < right.a;
return left.b < right.b;
}
@@ -608,11 +608,11 @@ namespace Godot
public static bool operator >(Color left, Color right)
{
- if (left.r == right.r)
+ if (Mathf.IsEqualApprox(left.r, right.r))
{
- if (left.g == right.g)
+ if (Mathf.IsEqualApprox(left.g, right.g))
{
- if (left.b == right.b)
+ if (Mathf.IsEqualApprox(left.b, right.b))
return left.a > right.a;
return left.b > right.b;
}
@@ -635,7 +635,7 @@ namespace Godot
public bool Equals(Color other)
{
- return r == other.r && g == other.g && b == other.b && a == other.a;
+ return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a);
}
public override int GetHashCode()
diff --git a/modules/mono/glue/Managed/Files/DynamicObject.cs b/modules/mono/glue/Managed/Files/DynamicObject.cs
index 9860feafdd..a0f105d55e 100644
--- a/modules/mono/glue/Managed/Files/DynamicObject.cs
+++ b/modules/mono/glue/Managed/Files/DynamicObject.cs
@@ -202,7 +202,7 @@ namespace Godot
//public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);
//public override bool TryDeleteMember(DeleteMemberBinder binder);
- // Invokation on the object itself, e.g.: obj(param)
+ // Invocation on the object itself, e.g.: obj(param)
//public override bool TryInvoke(InvokeBinder binder, object[] args, out object result);
// No unnary operations to handle
diff --git a/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs
index 366d89b1c2..5023725f17 100644
--- a/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs
+++ b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs
@@ -24,12 +24,12 @@ namespace Godot
public T GetOwner<T>() where T : class
{
- return (T)(object)GetOwner();
+ return (T)(object)Owner;
}
public T GetOwnerOrNull<T>() where T : class
{
- return GetOwner() as T;
+ return Owner as T;
}
public T GetParent<T>() where T : class
diff --git a/modules/mono/glue/Managed/Files/GD.cs b/modules/mono/glue/Managed/Files/GD.cs
index d968f8a78f..2068099ac6 100644
--- a/modules/mono/glue/Managed/Files/GD.cs
+++ b/modules/mono/glue/Managed/Files/GD.cs
@@ -83,7 +83,7 @@ namespace Godot
public static void Print(params object[] what)
{
- godot_icall_GD_print(what);
+ godot_icall_GD_print(Array.ConvertAll(what, x => x.ToString()));
}
public static void PrintStack()
@@ -93,25 +93,25 @@ namespace Godot
public static void PrintErr(params object[] what)
{
- godot_icall_GD_printerr(what);
+ godot_icall_GD_printerr(Array.ConvertAll(what, x => x.ToString()));
}
public static void PrintRaw(params object[] what)
{
- godot_icall_GD_printraw(what);
+ godot_icall_GD_printraw(Array.ConvertAll(what, x => x.ToString()));
}
public static void PrintS(params object[] what)
{
- godot_icall_GD_prints(what);
+ godot_icall_GD_prints(Array.ConvertAll(what, x => x.ToString()));
}
public static void PrintT(params object[] what)
{
- godot_icall_GD_printt(what);
+ godot_icall_GD_printt(Array.ConvertAll(what, x => x.ToString()));
}
- public static double Randf()
+ public static float Randf()
{
return godot_icall_GD_randf();
}
@@ -224,7 +224,7 @@ namespace Godot
internal extern static void godot_icall_GD_printt(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static double godot_icall_GD_randf();
+ internal extern static float godot_icall_GD_randf();
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static uint godot_icall_GD_randi();
@@ -232,6 +232,7 @@ namespace Godot
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_GD_randomize();
+
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static double godot_icall_GD_rand_range(double from, double to);
diff --git a/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs b/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs
new file mode 100644
index 0000000000..c3fa2f3e82
--- /dev/null
+++ b/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs
@@ -0,0 +1,8 @@
+namespace Godot
+{
+ public interface ISerializationListener
+ {
+ void OnBeforeSerialize();
+ void OnAfterDeserialize();
+ }
+}
diff --git a/modules/mono/glue/Managed/Files/MarshalUtils.cs b/modules/mono/glue/Managed/Files/MarshalUtils.cs
index 7e72b0edb5..a1d63a62ef 100644
--- a/modules/mono/glue/Managed/Files/MarshalUtils.cs
+++ b/modules/mono/glue/Managed/Files/MarshalUtils.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
+using System.Collections.Generic;
namespace Godot
{
@@ -8,29 +9,151 @@ namespace Godot
static class MarshalUtils
{
+ /// <summary>
+ /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/>
+ /// is <see cref="Godot.Collections.Array{T}"/>; otherwise returns <see langword="false"/>.
+ /// </summary>
+ /// <exception cref="System.InvalidOperationException">
+ /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false.
+ /// </exception>
static bool TypeIsGenericArray(Type type)
{
return type.GetGenericTypeDefinition() == typeof(Godot.Collections.Array<>);
}
+ /// <summary>
+ /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/>
+ /// is <see cref="Godot.Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>.
+ /// </summary>
+ /// <exception cref="System.InvalidOperationException">
+ /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false.
+ /// </exception>
static bool TypeIsGenericDictionary(Type type)
{
return type.GetGenericTypeDefinition() == typeof(Godot.Collections.Dictionary<,>);
}
- static void ArrayGetElementType(Type type, out Type elementType)
+ static void ArrayGetElementType(Type arrayType, out Type elementType)
{
- elementType = type.GetGenericArguments()[0];
+ elementType = arrayType.GetGenericArguments()[0];
}
- static void DictionaryGetKeyValueTypes(Type type, out Type keyType, out Type valueType)
+ static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType)
{
- var genericArgs = type.GetGenericArguments();
-
+ var genericArgs = dictionaryType.GetGenericArguments();
keyType = genericArgs[0];
valueType = genericArgs[1];
}
+ static bool GenericIEnumerableIsAssignableFromType(Type type)
+ {
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
+ return true;
+
+ foreach (var interfaceType in type.GetInterfaces())
+ {
+ if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
+ return true;
+ }
+
+ Type baseType = type.BaseType;
+
+ if (baseType == null)
+ return false;
+
+ return GenericIEnumerableIsAssignableFromType(baseType);
+ }
+
+ static bool GenericIDictionaryIsAssignableFromType(Type type)
+ {
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
+ return true;
+
+ foreach (var interfaceType in type.GetInterfaces())
+ {
+ if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
+ return true;
+ }
+
+ Type baseType = type.BaseType;
+
+ if (baseType == null)
+ return false;
+
+ return GenericIDictionaryIsAssignableFromType(baseType);
+ }
+
+ static bool GenericIEnumerableIsAssignableFromType(Type type, out Type elementType)
+ {
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
+ {
+ elementType = type.GetGenericArguments()[0];
+ return true;
+ }
+
+ foreach (var interfaceType in type.GetInterfaces())
+ {
+ if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
+ {
+ elementType = interfaceType.GetGenericArguments()[0];
+ return true;
+ }
+ }
+
+ Type baseType = type.BaseType;
+
+ if (baseType == null)
+ {
+ elementType = null;
+ return false;
+ }
+
+ return GenericIEnumerableIsAssignableFromType(baseType, out elementType);
+ }
+
+ static bool GenericIDictionaryIsAssignableFromType(Type type, out Type keyType, out Type valueType)
+ {
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
+ {
+ var genericArgs = type.GetGenericArguments();
+ keyType = genericArgs[0];
+ valueType = genericArgs[1];
+ return true;
+ }
+
+ foreach (var interfaceType in type.GetInterfaces())
+ {
+ if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
+ {
+ var genericArgs = interfaceType.GetGenericArguments();
+ keyType = genericArgs[0];
+ valueType = genericArgs[1];
+ return true;
+ }
+ }
+
+ Type baseType = type.BaseType;
+
+ if (baseType == null)
+ {
+ keyType = null;
+ valueType = null;
+ return false;
+ }
+
+ return GenericIDictionaryIsAssignableFromType(baseType, out keyType, out valueType);
+ }
+
+ static Type MakeGenericArrayType(Type elemType)
+ {
+ return typeof(Godot.Collections.Array<>).MakeGenericType(elemType);
+ }
+
+ static Type MakeGenericDictionaryType(Type keyType, Type valueType)
+ {
+ return typeof(Godot.Collections.Dictionary<,>).MakeGenericType(keyType, valueType);
+ }
+
// TODO Add support for IEnumerable<T> and IDictionary<TKey, TValue>
// TODO: EnumerableToArray and IDictionaryToDictionary can be optimized
@@ -64,5 +187,26 @@ namespace Godot
Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, entry.Key, entry.Value);
}
}
+
+ internal static void GenericIDictionaryToDictionary(object dictionary, IntPtr godotDictionaryPtr)
+ {
+#if DEBUG
+ if (!GenericIDictionaryIsAssignableFromType(dictionary.GetType()))
+ throw new InvalidOperationException("The type does not implement IDictionary<,>");
+#endif
+
+ // TODO: Can we optimize this?
+
+ var keys = ((IEnumerable)dictionary.GetType().GetProperty("Keys").GetValue(dictionary)).GetEnumerator();
+ var values = ((IEnumerable)dictionary.GetType().GetProperty("Values").GetValue(dictionary)).GetEnumerator();
+
+ while (keys.MoveNext() && values.MoveNext())
+ {
+ object key = keys.Current;
+ object value = values.Current;
+
+ Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, key, value);
+ }
+ }
}
}
diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs
index a064278237..2d8c63fe7f 100644
--- a/modules/mono/glue/Managed/Files/Mathf.cs
+++ b/modules/mono/glue/Managed/Files/Mathf.cs
@@ -44,9 +44,9 @@ namespace Godot
return (real_t)Math.Atan(s);
}
- public static real_t Atan2(real_t x, real_t y)
+ public static real_t Atan2(real_t y, real_t x)
{
- return (real_t)Math.Atan2(x, y);
+ return (real_t)Math.Atan2(y, x);
}
public static Vector2 Cartesian2Polar(real_t x, real_t y)
@@ -79,14 +79,27 @@ namespace Godot
return (real_t)Math.Cosh(s);
}
- public static int Decimals(real_t step)
- {
- return Decimals((decimal)step);
- }
-
- public static int Decimals(decimal step)
- {
- return BitConverter.GetBytes(decimal.GetBits(step)[3])[2];
+ public static int StepDecimals(real_t step)
+ {
+ double[] sd = new double[] {
+ 0.9999,
+ 0.09999,
+ 0.009999,
+ 0.0009999,
+ 0.00009999,
+ 0.000009999,
+ 0.0000009999,
+ 0.00000009999,
+ 0.000000009999,
+ };
+ double abs = Mathf.Abs(step);
+ double decs = abs - (int)abs; // Strip away integer part
+ for (int i = 0; i < sd.Length; i++) {
+ if (decs >= sd[i]) {
+ return i;
+ }
+ }
+ return 0;
}
public static real_t Deg2Rad(real_t deg)
@@ -143,6 +156,15 @@ namespace Godot
return (weight - from) / (to - from);
}
+ public static bool IsEqualApprox(real_t a, real_t b)
+ {
+ real_t tolerance = Epsilon * Abs(a);
+ if (tolerance < Epsilon) {
+ tolerance = Epsilon;
+ }
+ return Abs(a - b) < tolerance;
+ }
+
public static bool IsInf(real_t s)
{
return real_t.IsInfinity(s);
@@ -153,6 +175,11 @@ namespace Godot
return real_t.IsNaN(s);
}
+ public static bool IsZeroApprox(real_t s)
+ {
+ return Abs(s) < Epsilon;
+ }
+
public static real_t Lerp(real_t from, real_t to, real_t weight)
{
return from + (to - from) * weight;
@@ -183,6 +210,11 @@ namespace Godot
return a < b ? a : b;
}
+ 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;
+ }
+
public static int NearestPo2(int value)
{
value--;
diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs
index 414762f7b1..b96f01bc2e 100644
--- a/modules/mono/glue/Managed/Files/MathfEx.cs
+++ b/modules/mono/glue/Managed/Files/MathfEx.cs
@@ -21,6 +21,16 @@ namespace Godot
public const real_t Epsilon = 1e-06f;
#endif
+ public static int DecimalCount(real_t s)
+ {
+ return DecimalCount((decimal)s);
+ }
+
+ public static int DecimalCount(decimal s)
+ {
+ return BitConverter.GetBytes(decimal.GetBits(s)[3])[2];
+ }
+
public static int CeilToInt(real_t s)
{
return (int)Math.Ceiling(s);
@@ -36,9 +46,9 @@ namespace Godot
return (int)Math.Round(s);
}
- public static bool IsEqualApprox(real_t a, real_t b, real_t ratio = Mathf.Epsilon)
+ public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance)
{
- return Abs(a - b) < ratio;
+ return Abs(a - b) < tolerance;
}
}
} \ No newline at end of file
diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs
index f11cd490a9..e16d4315be 100644
--- a/modules/mono/glue/Managed/Files/Plane.cs
+++ b/modules/mono/glue/Managed/Files/Plane.cs
@@ -200,7 +200,7 @@ namespace Godot
public bool Equals(Plane other)
{
- return _normal == other._normal && D == other.D;
+ return _normal == other._normal && Mathf.IsEqualApprox(D, other.D);
}
public override int GetHashCode()
diff --git a/modules/mono/glue/Managed/Files/Quat.cs b/modules/mono/glue/Managed/Files/Quat.cs
index d0c15146a5..0d4349084a 100644
--- a/modules/mono/glue/Managed/Files/Quat.cs
+++ b/modules/mono/glue/Managed/Files/Quat.cs
@@ -358,7 +358,7 @@ namespace Godot
public bool Equals(Quat other)
{
- return x == other.x && y == other.y && z == other.z && w == other.w;
+ return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w);
}
public override int GetHashCode()
diff --git a/modules/mono/glue/Managed/Files/StringExtensions.cs b/modules/mono/glue/Managed/Files/StringExtensions.cs
index c194facd0b..b43034fbb5 100644
--- a/modules/mono/glue/Managed/Files/StringExtensions.cs
+++ b/modules/mono/glue/Managed/Files/StringExtensions.cs
@@ -299,14 +299,14 @@ namespace Godot
if (basepos != -1)
{
var end = basepos + 3;
- rs = instance.Substring(end, instance.Length);
+ rs = instance.Substring(end);
@base = instance.Substring(0, end);
}
else
{
if (instance.BeginsWith("/"))
{
- rs = instance.Substring(1, instance.Length);
+ rs = instance.Substring(1);
@base = "/";
}
else
@@ -333,7 +333,7 @@ namespace Godot
if (sep == -1)
return instance;
- return instance.Substring(sep + 1, instance.Length);
+ return instance.Substring(sep + 1);
}
// <summary>
@@ -911,7 +911,8 @@ namespace Godot
// </summary>
public static string Substr(this string instance, int from, int len)
{
- return instance.Substring(from, len);
+ int max = instance.Length - from;
+ return instance.Substring(from, len > max ? max : len);
}
// <summary>
diff --git a/modules/mono/glue/Managed/Files/Transform2D.cs b/modules/mono/glue/Managed/Files/Transform2D.cs
index f7bb41d523..33ff286769 100644
--- a/modules/mono/glue/Managed/Files/Transform2D.cs
+++ b/modules/mono/glue/Managed/Files/Transform2D.cs
@@ -298,6 +298,7 @@ namespace Godot
origin = originPos;
}
+ // Arguments are named such that xy is equal to calling x.y
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);
diff --git a/modules/mono/glue/Managed/Files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs
index 908162ec45..a7f26283a7 100644
--- a/modules/mono/glue/Managed/Files/Vector2.cs
+++ b/modules/mono/glue/Managed/Files/Vector2.cs
@@ -52,11 +52,15 @@ namespace Godot
internal void Normalize()
{
- real_t length = x * x + y * y;
+ real_t lengthsq = LengthSquared();
- if (length != 0f)
+ if (lengthsq == 0)
{
- length = Mathf.Sqrt(length);
+ x = y = 0f;
+ }
+ else
+ {
+ real_t length = Mathf.Sqrt(lengthsq);
x /= length;
y /= length;
}
@@ -182,11 +186,19 @@ namespace Godot
return res;
}
+ public Vector2 MoveToward(Vector2 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;
+ }
+
public Vector2 Normalized()
{
- var result = this;
- result.Normalize();
- return result;
+ var v = this;
+ v.Normalize();
+ return v;
}
public Vector2 Project(Vector2 onNormal)
@@ -343,7 +355,7 @@ namespace Godot
public static bool operator <(Vector2 left, Vector2 right)
{
- if (left.x.Equals(right.x))
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
return left.y < right.y;
}
@@ -353,7 +365,7 @@ namespace Godot
public static bool operator >(Vector2 left, Vector2 right)
{
- if (left.x.Equals(right.x))
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
return left.y > right.y;
}
@@ -363,7 +375,7 @@ namespace Godot
public static bool operator <=(Vector2 left, Vector2 right)
{
- if (left.x.Equals(right.x))
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
return left.y <= right.y;
}
@@ -373,7 +385,7 @@ namespace Godot
public static bool operator >=(Vector2 left, Vector2 right)
{
- if (left.x.Equals(right.x))
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
return left.y >= right.y;
}
@@ -393,7 +405,7 @@ namespace Godot
public bool Equals(Vector2 other)
{
- return x == other.x && y == other.y;
+ return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y);
}
public override int GetHashCode()
diff --git a/modules/mono/glue/Managed/Files/Vector3.cs b/modules/mono/glue/Managed/Files/Vector3.cs
index 0c96d346a9..16803ae55c 100644
--- a/modules/mono/glue/Managed/Files/Vector3.cs
+++ b/modules/mono/glue/Managed/Files/Vector3.cs
@@ -65,14 +65,15 @@ namespace Godot
internal void Normalize()
{
- real_t length = Length();
+ real_t lengthsq = LengthSquared();
- if (length == 0f)
+ if (lengthsq == 0)
{
x = y = z = 0f;
}
else
{
+ real_t length = Mathf.Sqrt(lengthsq);
x /= length;
y /= length;
z /= length;
@@ -189,6 +190,14 @@ namespace Godot
);
}
+ 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;
+ }
+
public Axis MaxAxis()
{
return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X);
@@ -397,9 +406,9 @@ namespace Godot
public static bool operator <(Vector3 left, Vector3 right)
{
- if (left.x == right.x)
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
- if (left.y == right.y)
+ if (Mathf.IsEqualApprox(left.y, right.y))
return left.z < right.z;
return left.y < right.y;
}
@@ -409,9 +418,9 @@ namespace Godot
public static bool operator >(Vector3 left, Vector3 right)
{
- if (left.x == right.x)
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
- if (left.y == right.y)
+ if (Mathf.IsEqualApprox(left.y, right.y))
return left.z > right.z;
return left.y > right.y;
}
@@ -421,9 +430,9 @@ namespace Godot
public static bool operator <=(Vector3 left, Vector3 right)
{
- if (left.x == right.x)
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
- if (left.y == right.y)
+ if (Mathf.IsEqualApprox(left.y, right.y))
return left.z <= right.z;
return left.y < right.y;
}
@@ -433,9 +442,9 @@ namespace Godot
public static bool operator >=(Vector3 left, Vector3 right)
{
- if (left.x == right.x)
+ if (Mathf.IsEqualApprox(left.x, right.x))
{
- if (left.y == right.y)
+ if (Mathf.IsEqualApprox(left.y, right.y))
return left.z >= right.z;
return left.y > right.y;
}
@@ -455,7 +464,7 @@ namespace Godot
public bool Equals(Vector3 other)
{
- return x == other.x && y == other.y && z == other.z;
+ return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z);
}
public override int GetHashCode()
diff --git a/modules/mono/glue/Managed/IgnoredFiles/Node.cs b/modules/mono/glue/Managed/IgnoredFiles/Node.cs
index 99ba0f827a..cff61b1e0b 100644
--- a/modules/mono/glue/Managed/IgnoredFiles/Node.cs
+++ b/modules/mono/glue/Managed/IgnoredFiles/Node.cs
@@ -15,9 +15,10 @@ namespace Godot
throw new NotImplementedException();
}
- public Node GetOwner()
+ public Node Owner
{
- throw new NotImplementedException();
+ get => throw new NotImplementedException();
+ set => throw new NotImplementedException();
}
public Node GetParent()
diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp
index 7385014a53..6d85f55b97 100644
--- a/modules/mono/glue/base_object_glue.cpp
+++ b/modules/mono/glue/base_object_glue.cpp
@@ -166,7 +166,7 @@ MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) {
int i = 0;
for (List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) {
MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get().name);
- mono_array_set(result, MonoString *, i, boxed);
+ mono_array_setref(result, i, boxed);
i++;
}
@@ -219,7 +219,18 @@ MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *
}
MonoString *godot_icall_Object_ToString(Object *p_ptr) {
- return GDMonoMarshal::mono_string_from_godot(Variant(p_ptr).operator String());
+#ifdef DEBUG_ENABLED
+ // Cannot happen in C#; would get an ObjectDisposedException instead.
+ CRASH_COND(p_ptr == NULL);
+
+ if (ScriptDebugger::get_singleton() && !Object::cast_to<Reference>(p_ptr)) { // Only if debugging!
+ // Cannot happen either in C#; the handle is nullified when the object is destroyed
+ CRASH_COND(!ObjectDB::instance_validate(p_ptr));
+ }
+#endif
+
+ String result = "[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]";
+ return GDMonoMarshal::mono_string_from_godot(result);
}
void godot_register_object_icalls() {
diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp
index 4aef5684fd..e67c8b9ad9 100644
--- a/modules/mono/glue/collections_glue.cpp
+++ b/modules/mono/glue/collections_glue.cpp
@@ -46,7 +46,7 @@ void godot_icall_Array_Dtor(Array *ptr) {
}
MonoObject *godot_icall_Array_At(Array *ptr, int index) {
- if (index < 0 || index > ptr->size()) {
+ if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return NULL;
}
@@ -54,7 +54,7 @@ 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) {
- if (index < 0 || index > ptr->size()) {
+ if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return NULL;
}
@@ -62,7 +62,7 @@ MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_en
}
void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) {
- if (index < 0 || index > ptr->size()) {
+ if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return;
}
@@ -124,7 +124,7 @@ MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) {
}
void godot_icall_Array_RemoveAt(Array *ptr, int index) {
- if (index < 0 || index > ptr->size()) {
+ if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return;
}
@@ -162,7 +162,7 @@ MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) {
#ifdef DEBUG_ENABLED
CRASH_COND(!exc);
#endif
- GDMonoUtils::runtime_object_init(exc);
+ GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException));
GDMonoUtils::set_pending_exception((MonoException *)exc);
return NULL;
}
@@ -176,7 +176,7 @@ MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject
#ifdef DEBUG_ENABLED
CRASH_COND(!exc);
#endif
- GDMonoUtils::runtime_object_init(exc);
+ GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException));
GDMonoUtils::set_pending_exception((MonoException *)exc);
return NULL;
}
diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp
index d756131ac9..7c30092855 100644
--- a/modules/mono/glue/gd_glue.cpp
+++ b/modules/mono/glue/gd_glue.cpp
@@ -115,7 +115,7 @@ void godot_icall_GD_printt(MonoArray *p_what) {
print_line(str);
}
-double godot_icall_GD_randf() {
+float godot_icall_GD_randf() {
return Math::randf();
}
diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h
index 910979aae3..d4e20e2887 100644
--- a/modules/mono/glue/gd_glue.h
+++ b/modules/mono/glue/gd_glue.h
@@ -53,7 +53,7 @@ void godot_icall_GD_prints(MonoArray *p_what);
void godot_icall_GD_printt(MonoArray *p_what);
-double godot_icall_GD_randf();
+float godot_icall_GD_randf();
uint32_t godot_icall_GD_randi();
diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp
index a5c72160d7..e9373fb486 100644
--- a/modules/mono/glue/string_glue.cpp
+++ b/modules/mono/glue/string_glue.cpp
@@ -68,12 +68,12 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) {
}
void godot_register_string_icalls() {
- mono_add_internal_call("Godot.String::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer);
- mono_add_internal_call("Godot.String::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text);
- mono_add_internal_call("Godot.String::godot_icall_String_rfind", (void *)godot_icall_String_rfind);
- mono_add_internal_call("Godot.String::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn);
- mono_add_internal_call("Godot.String::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer);
- mono_add_internal_call("Godot.String::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text);
+ mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer);
+ mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text);
+ mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", (void *)godot_icall_String_rfind);
+ mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn);
+ mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer);
+ mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h
index 0d3b96d789..4ad4088514 100644
--- a/modules/mono/godotsharp_defs.h
+++ b/modules/mono/godotsharp_defs.h
@@ -39,7 +39,8 @@
#define API_SOLUTION_NAME "GodotSharp"
#define CORE_API_ASSEMBLY_NAME "GodotSharp"
#define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor"
-#define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools"
+#define TOOLS_ASSEMBLY_NAME "GodotTools"
+#define TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME "GodotTools.ProjectEditor"
#define BINDINGS_CLASS_NATIVECALLS "NativeCalls"
#define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls"
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 09a1fc6fbc..4b2525c692 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -39,6 +39,10 @@
#include "editor/editor_settings.h"
#endif
+#ifdef __ANDROID__
+#include "utils/android_utils.h"
+#endif
+
namespace GodotSharpDirs {
String _get_expected_build_config() {
@@ -55,6 +59,20 @@ String _get_expected_build_config() {
#endif
}
+String _get_expected_api_build_config() {
+#ifdef TOOLS_ENABLED
+ return "Debug";
+#else
+
+#ifdef DEBUG_ENABLED
+ return "Debug";
+#else
+ return "Release";
+#endif
+
+#endif
+}
+
String _get_mono_user_dir() {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
@@ -84,6 +102,7 @@ class _GodotSharpDirs {
public:
String res_data_dir;
String res_metadata_dir;
+ String res_assemblies_base_dir;
String res_assemblies_dir;
String res_config_dir;
String res_temp_dir;
@@ -114,7 +133,8 @@ private:
_GodotSharpDirs() {
res_data_dir = "res://.mono";
res_metadata_dir = res_data_dir.plus_file("metadata");
- res_assemblies_dir = res_data_dir.plus_file("assemblies");
+ res_assemblies_base_dir = res_data_dir.plus_file("assemblies");
+ res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config());
res_config_dir = res_data_dir.plus_file("etc").plus_file("mono");
// TODO use paths from csproj
@@ -129,15 +149,16 @@ private:
mono_solutions_dir = mono_user_dir.plus_file("solutions");
build_logs_dir = mono_user_dir.plus_file("build_logs");
- String name = ProjectSettings::get_singleton()->get("application/config/name");
- if (name.empty()) {
- name = "UnnamedProject";
+ String appname = ProjectSettings::get_singleton()->get("application/config/name");
+ String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+ if (appname_safe.empty()) {
+ appname_safe = "UnnamedProject";
}
String base_path = ProjectSettings::get_singleton()->globalize_path("res://");
- sln_filepath = base_path.plus_file(name + ".sln");
- csproj_filepath = base_path.plus_file(name + ".csproj");
+ sln_filepath = base_path.plus_file(appname_safe + ".sln");
+ csproj_filepath = base_path.plus_file(appname_safe + ".csproj");
#endif
String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
@@ -150,7 +171,12 @@ private:
String data_mono_root_dir = data_dir_root.plus_file("Mono");
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
+
+#if __ANDROID__
+ data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
+#else
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
+#endif
#ifdef WINDOWS_ENABLED
data_mono_bin_dir = data_mono_root_dir.plus_file("bin");
@@ -173,15 +199,21 @@ private:
#else
- String appname = OS::get_singleton()->get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name"));
- String data_dir_root = exe_dir.plus_file("data_" + appname);
+ String appname = ProjectSettings::get_singleton()->get("application/config/name");
+ String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+ String data_dir_root = exe_dir.plus_file("data_" + appname_safe);
if (!DirAccess::exists(data_dir_root)) {
data_dir_root = exe_dir.plus_file("data_Godot");
}
String data_mono_root_dir = data_dir_root.plus_file("Mono");
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
+
+#if __ANDROID__
+ data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
+#else
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
+#endif
#ifdef WINDOWS_ENABLED
data_mono_bin_dir = data_mono_root_dir.plus_file("bin");
@@ -215,6 +247,10 @@ String get_res_metadata_dir() {
return _GodotSharpDirs::get_singleton().res_metadata_dir;
}
+String get_res_assemblies_base_dir() {
+ return _GodotSharpDirs::get_singleton().res_assemblies_base_dir;
+}
+
String get_res_assemblies_dir() {
return _GodotSharpDirs::get_singleton().res_assemblies_dir;
}
diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h
index 556df959e2..ff51888d1c 100644
--- a/modules/mono/godotsharp_dirs.h
+++ b/modules/mono/godotsharp_dirs.h
@@ -37,6 +37,7 @@ namespace GodotSharpDirs {
String get_res_data_dir();
String get_res_metadata_dir();
+String get_res_assemblies_base_dir();
String get_res_assemblies_dir();
String get_res_config_dir();
String get_res_temp_dir();
diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h
index 63b61aff18..60a1eed212 100644
--- a/modules/mono/mono_gc_handle.h
+++ b/modules/mono/mono_gc_handle.h
@@ -37,7 +37,7 @@
class MonoGCHandle : public Reference {
- GDCLASS(MonoGCHandle, Reference)
+ GDCLASS(MonoGCHandle, Reference);
bool released;
bool weak;
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index bfb6c13224..06fbae019c 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -52,10 +52,13 @@
#include "gd_mono_utils.h"
#ifdef TOOLS_ENABLED
-#include "../editor/godotsharp_editor.h"
#include "main/main.h"
#endif
+#ifdef ANDROID_ENABLED
+#include "android_mono_config.gen.h"
+#endif
+
#define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. " \
"This error is expected if you just upgraded to a newer Godot version. " \
"Building the project will update the assembly to the correct version."
@@ -95,7 +98,7 @@ void gdmono_profiler_init() {
#ifdef DEBUG_ENABLED
-static bool _wait_for_debugger_msecs(uint32_t p_msecs) {
+bool _wait_for_debugger_msecs(uint32_t p_msecs) {
do {
if (mono_is_debugger_attached())
@@ -125,16 +128,17 @@ void gdmono_debug_init() {
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
+ CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
+
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() ||
ProjectSettings::get_singleton()->get_resource_path().empty() ||
Main::is_project_manager()) {
- return;
+ if (da_args.size() == 0)
+ return;
}
#endif
- CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
-
if (da_args.length() == 0) {
da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) +
",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n"))
@@ -203,6 +207,10 @@ void GDMono::initialize() {
print_verbose("Mono: Initializing module...");
+ char *runtime_build_info = mono_get_runtime_build_info();
+ print_verbose("Mono JIT compiler version " + String(runtime_build_info));
+ mono_free(runtime_build_info);
+
#ifdef DEBUG_METHODS_ENABLED
_initialize_and_check_api_hashes();
#endif
@@ -233,9 +241,9 @@ void GDMono::initialize() {
locations.push_back("/usr/local/var/homebrew/linked/mono/");
for (int i = 0; i < locations.size(); i++) {
- String hint_assembly_rootdir = path_join(locations[i], "lib");
- String hint_mscorlib_path = path_join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll");
- String hint_config_dir = path_join(locations[i], "etc");
+ String hint_assembly_rootdir = path::join(locations[i], "lib");
+ String hint_mscorlib_path = path::join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll");
+ String hint_config_dir = path::join(locations[i], "etc");
if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) {
assembly_rootdir = hint_assembly_rootdir;
@@ -251,17 +259,19 @@ void GDMono::initialize() {
String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir();
#ifdef TOOLS_ENABLED
- if (DirAccess::exists(bundled_assembly_rootdir) && DirAccess::exists(bundled_config_dir)) {
+ if (DirAccess::exists(bundled_assembly_rootdir)) {
assembly_rootdir = bundled_assembly_rootdir;
+ }
+
+ if (DirAccess::exists(bundled_config_dir)) {
config_dir = bundled_config_dir;
}
#ifdef WINDOWS_ENABLED
if (assembly_rootdir.empty() || config_dir.empty()) {
+ ERR_PRINT("Cannot find Mono in the registry");
// Assertion: if they are not set, then they weren't found in the registry
CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0);
-
- ERR_PRINT("Cannot find Mono in the registry");
}
#endif // WINDOWS_ENABLED
@@ -285,7 +295,11 @@ void GDMono::initialize() {
gdmono_debug_init();
#endif
+#ifdef ANDROID_ENABLED
+ mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data());
+#else
mono_config_parse(NULL);
+#endif
mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL);
@@ -329,18 +343,6 @@ void GDMono::initialize() {
ERR_EXPLAIN("Mono: Failed to load mscorlib assembly");
ERR_FAIL_COND(!_load_corlib_assembly());
-#ifdef TOOLS_ENABLED
- // The tools domain must be loaded here, before the scripts domain.
- // Otherwise domain unload on the scripts domain will hang indefinitely.
-
- ERR_EXPLAIN("Mono: Failed to load tools domain");
- ERR_FAIL_COND(_load_tools_domain() != OK);
-
- // TODO move to editor init callback, and do it lazily when required before editor init (e.g.: bindings generation)
- ERR_EXPLAIN("Mono: Failed to load Editor Tools assembly");
- ERR_FAIL_COND(!_load_editor_tools_assembly());
-#endif
-
ERR_EXPLAIN("Mono: Failed to load scripts domain");
ERR_FAIL_COND(_load_scripts_domain() != OK);
@@ -355,8 +357,15 @@ void GDMono::initialize() {
// The following assemblies are not required at initialization
#ifdef MONO_GLUE_ENABLED
if (_load_api_assemblies()) {
- // Everything is fine with the api assemblies, load the project assembly
+ // Everything is fine with the api assemblies, load the tools and project assemblies
+
+#if defined(TOOLS_ENABLED)
+ ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
+ ERR_FAIL_COND(!_load_tools_assemblies());
+#endif
+
_load_project_assembly();
+
} else {
if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated))
#ifdef TOOLS_ENABLED
@@ -417,10 +426,6 @@ void GDMono::_register_internal_calls() {
#ifdef MONO_GLUE_ENABLED
GodotSharpBindings::register_generated_icalls();
#endif
-
-#ifdef TOOLS_ENABLED
- GodotSharpEditor::register_internal_calls();
-#endif
}
void GDMono::_initialize_and_check_api_hashes() {
@@ -559,6 +564,52 @@ bool GDMono::_load_corlib_assembly() {
return success;
}
+#ifdef TOOLS_ENABLED
+static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) {
+
+ // Create destination directory if needed
+ if (!DirAccess::exists(p_dst_dir)) {
+ DirAccess *da = DirAccess::create_for_path(p_dst_dir);
+ Error err = da->make_dir_recursive(p_dst_dir);
+ memdelete(da);
+
+ if (err != OK) {
+ ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err));
+ return false;
+ }
+ }
+
+ String assembly_file = p_assembly_name + ".dll";
+ String assembly_src = p_src_dir.plus_file(assembly_file);
+ String assembly_dst = p_dst_dir.plus_file(assembly_file);
+
+ if (!FileAccess::exists(assembly_dst) ||
+ FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
+ GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ String xml_file = p_assembly_name + ".xml";
+ if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
+ WARN_PRINTS("Failed to copy " + xml_file);
+
+ String pdb_file = p_assembly_name + ".pdb";
+ if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK)
+ WARN_PRINTS("Failed to copy " + pdb_file);
+
+ Error err = da->copy(assembly_src, assembly_dst);
+
+ if (err != OK) {
+ ERR_PRINTS("Failed to copy " + assembly_file);
+ return false;
+ }
+
+ GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
+ }
+
+ return true;
+}
+#endif
+
bool GDMono::_load_core_api_assembly() {
if (core_api_assembly)
@@ -566,19 +617,31 @@ bool GDMono::_load_core_api_assembly() {
#ifdef TOOLS_ENABLED
if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) {
- print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated");
- return false;
+ String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
+ String prebuilt_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+ String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_CORE);
+
+ if (!FileAccess::exists(prebuilt_dll_path) ||
+ FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
+ print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated");
+ return false;
+ } else {
+ // Copy the prebuilt Api
+ String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
+ if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, CORE_API_ASSEMBLY_NAME, APIAssembly::API_CORE) ||
+ !copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
+ print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Core API assembly because it was invalidated");
+ return false;
+ }
+ }
}
#endif
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
- if (!FileAccess::exists(assembly_path))
- return false;
-
- bool success = load_assembly_from(CORE_API_ASSEMBLY_NAME,
- assembly_path,
- &core_api_assembly);
+ bool success = (FileAccess::exists(assembly_path) &&
+ load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly)) ||
+ load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
@@ -606,18 +669,29 @@ bool GDMono::_load_editor_api_assembly() {
return true;
if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) {
- print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated");
- return false;
+ String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
+ String prebuilt_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+ String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_EDITOR);
+
+ if (!FileAccess::exists(prebuilt_dll_path) ||
+ FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
+ print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated");
+ return false;
+ } else {
+ // Copy the prebuilt editor Api (no need to copy the core api if we got to this point)
+ String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
+ if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
+ print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Editor API assembly because it was invalidated");
+ return false;
+ }
+ }
}
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
- if (!FileAccess::exists(assembly_path))
- return false;
-
- bool success = load_assembly_from(EDITOR_API_ASSEMBLY_NAME,
- assembly_path,
- &editor_api_assembly);
+ bool success = (FileAccess::exists(assembly_path) &&
+ load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly)) ||
+ load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
@@ -633,14 +707,15 @@ bool GDMono::_load_editor_api_assembly() {
#endif
#ifdef TOOLS_ENABLED
-bool GDMono::_load_editor_tools_assembly() {
+bool GDMono::_load_tools_assemblies() {
- if (editor_tools_assembly)
+ if (tools_assembly && tools_project_editor_assembly)
return true;
- _GDMONO_SCOPE_DOMAIN_(tools_domain)
+ bool success = load_assembly(TOOLS_ASSEMBLY_NAME, &tools_assembly) &&
+ load_assembly(TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME, &tools_project_editor_assembly);
- return load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly);
+ return success;
}
#endif
@@ -649,17 +724,16 @@ bool GDMono::_load_project_assembly() {
if (project_assembly)
return true;
- String name = ProjectSettings::get_singleton()->get("application/config/name");
- if (name.empty()) {
- name = "UnnamedProject";
+ String appname = ProjectSettings::get_singleton()->get("application/config/name");
+ String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+ if (appname_safe.empty()) {
+ appname_safe = "UnnamedProject";
}
- bool success = load_assembly(name, &project_assembly);
+ bool success = load_assembly(appname_safe, &project_assembly);
if (success) {
mono_assembly_set_main(project_assembly->get_assembly());
-
- CSharpLanguage::get_singleton()->project_assembly_loaded();
} else {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load project assembly");
@@ -772,6 +846,14 @@ bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type)
return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time;
}
+
+String GDMono::get_invalidated_api_assembly_path(APIAssembly::Type p_api_type) {
+
+ return GodotSharpDirs::get_res_assemblies_dir()
+ .plus_file(p_api_type == APIAssembly::API_CORE ?
+ CORE_API_ASSEMBLY_NAME ".dll" :
+ EDITOR_API_ASSEMBLY_NAME ".dll");
+}
#endif
Error GDMono::_load_scripts_domain() {
@@ -809,12 +891,16 @@ Error GDMono::_unload_scripts_domain() {
mono_gc_collect(mono_gc_max_generation());
+ GDMonoUtils::clear_godot_api_cache();
+
_domain_assemblies_cleanup(mono_domain_get_id(scripts_domain));
core_api_assembly = NULL;
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
+ tools_assembly = NULL;
+ tools_project_editor_assembly = NULL;
#endif
core_api_assembly_out_of_sync = false;
@@ -837,22 +923,6 @@ Error GDMono::_unload_scripts_domain() {
return OK;
}
-#ifdef TOOLS_ENABLED
-Error GDMono::_load_tools_domain() {
-
- ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG);
-
- print_verbose("Mono: Loading tools domain...");
-
- tools_domain = GDMonoUtils::create_domain("GodotEngine.ToolsDomain");
-
- ERR_EXPLAIN("Mono: Could not create tools app domain");
- ERR_FAIL_NULL_V(tools_domain, ERR_CANT_CREATE);
-
- return OK;
-}
-#endif
-
#ifdef GD_MONO_HOT_RELOAD
Error GDMono::reload_scripts_domain() {
@@ -866,7 +936,7 @@ Error GDMono::reload_scripts_domain() {
}
}
- CSharpLanguage::get_singleton()->_uninitialize_script_bindings();
+ CSharpLanguage::get_singleton()->_on_scripts_domain_unloaded();
Error err = _load_scripts_domain();
if (err != OK) {
@@ -914,6 +984,11 @@ Error GDMono::reload_scripts_domain() {
}
}
+#ifdef TOOLS_ENABLED
+ ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
+ ERR_FAIL_COND_V(!_load_tools_assemblies(), ERR_CANT_OPEN);
+#endif
+
if (!_load_project_assembly()) {
return ERR_CANT_OPEN;
}
@@ -928,6 +1003,7 @@ Error GDMono::reload_scripts_domain() {
Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
CRASH_COND(p_domain == NULL);
+ CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead
String domain_name = mono_domain_get_friendly_name(p_domain);
@@ -944,18 +1020,12 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
_domain_assemblies_cleanup(mono_domain_get_id(p_domain));
-#ifdef TOOLS_ENABLED
- if (p_domain == tools_domain) {
- editor_tools_assembly = NULL;
- }
-#endif
-
MonoException *exc = NULL;
mono_domain_try_unload(p_domain, (MonoObject **)&exc);
if (exc) {
ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`");
- GDMonoUtils::debug_unhandled_exception(exc);
+ GDMonoUtils::debug_print_unhandled_exception(exc);
return FAILED;
}
@@ -986,6 +1056,22 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
return NULL;
}
+GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) {
+
+ uint32_t domain_id = mono_domain_get_id(mono_domain_get());
+ HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id];
+
+ const String *k = NULL;
+ while ((k = domain_assemblies.next(k))) {
+ GDMonoAssembly *assembly = domain_assemblies.get(*k);
+ GDMonoClass *klass = assembly->get_class(p_namespace, p_name);
+ if (klass)
+ return klass;
+ }
+
+ return NULL;
+}
+
void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) {
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id];
@@ -1026,9 +1112,6 @@ GDMono::GDMono() {
root_domain = NULL;
scripts_domain = NULL;
-#ifdef TOOLS_ENABLED
- tools_domain = NULL;
-#endif
core_api_assembly_out_of_sync = false;
#ifdef TOOLS_ENABLED
@@ -1040,7 +1123,8 @@ GDMono::GDMono() {
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
- editor_tools_assembly = NULL;
+ tools_assembly = NULL;
+ tools_project_editor_assembly = NULL;
#endif
api_core_hash = 0;
@@ -1052,16 +1136,6 @@ GDMono::GDMono() {
GDMono::~GDMono() {
if (is_runtime_initialized()) {
-
-#ifdef TOOLS_ENABLED
- if (tools_domain) {
- Error err = finalize_and_unload_domain(tools_domain);
- if (err != OK) {
- ERR_PRINT("Mono: Failed to unload tools domain");
- }
- }
-#endif
-
if (scripts_domain) {
Error err = _unload_scripts_domain();
if (err != OK) {
@@ -1080,8 +1154,6 @@ GDMono::~GDMono() {
}
assemblies.clear();
- GDMonoUtils::clear_cache();
-
print_verbose("Mono: Runtime cleanup...");
mono_jit_cleanup(root_domain);
@@ -1118,14 +1190,14 @@ int32_t _GodotSharp::get_domain_id() {
int32_t _GodotSharp::get_scripts_domain_id() {
- MonoDomain *domain = SCRIPTS_DOMAIN;
+ MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain();
CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method
return mono_domain_get_id(domain);
}
bool _GodotSharp::is_scripts_domain_loaded() {
- return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL;
+ return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != NULL;
}
bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) {
@@ -1147,7 +1219,7 @@ bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) {
if (!p_domain)
return true;
- if (p_domain == SCRIPTS_DOMAIN && GDMono::get_singleton()->is_finalizing_scripts_domain())
+ if (p_domain == GDMono::get_singleton()->get_scripts_domain() && GDMono::get_singleton()->is_finalizing_scripts_domain())
return true;
return mono_domain_is_unloading(p_domain);
}
@@ -1162,6 +1234,12 @@ bool _GodotSharp::is_runtime_initialized() {
return GDMono::get_singleton()->is_runtime_initialized();
}
+void _GodotSharp::_reload_assemblies(bool p_soft_reload) {
+#ifdef GD_MONO_HOT_RELOAD
+ CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload);
+#endif
+}
+
void _GodotSharp::_bind_methods() {
ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread);
@@ -1174,6 +1252,7 @@ void _GodotSharp::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down);
ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized);
+ ClassDB::bind_method(D_METHOD("_reload_assemblies"), &_GodotSharp::_reload_assemblies);
}
_GodotSharp::_GodotSharp() {
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 216c96a612..a926bf4126 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -78,11 +78,6 @@ struct Version {
String to_string(Type p_type);
} // namespace APIAssembly
-#define SCRIPTS_DOMAIN GDMono::get_singleton()->get_scripts_domain()
-#ifdef TOOLS_ENABLED
-#define TOOLS_DOMAIN GDMono::get_singleton()->get_tools_domain()
-#endif
-
class GDMono {
bool runtime_initialized;
@@ -90,9 +85,6 @@ class GDMono {
MonoDomain *root_domain;
MonoDomain *scripts_domain;
-#ifdef TOOLS_ENABLED
- MonoDomain *tools_domain;
-#endif
bool core_api_assembly_out_of_sync;
#ifdef TOOLS_ENABLED
@@ -104,7 +96,8 @@ class GDMono {
GDMonoAssembly *project_assembly;
#ifdef TOOLS_ENABLED
GDMonoAssembly *editor_api_assembly;
- GDMonoAssembly *editor_tools_assembly;
+ GDMonoAssembly *tools_assembly;
+ GDMonoAssembly *tools_project_editor_assembly;
#endif
HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
@@ -115,7 +108,7 @@ class GDMono {
bool _load_core_api_assembly();
#ifdef TOOLS_ENABLED
bool _load_editor_api_assembly();
- bool _load_editor_tools_assembly();
+ bool _load_tools_assemblies();
#endif
bool _load_project_assembly();
@@ -132,10 +125,6 @@ class GDMono {
Error _load_scripts_domain();
Error _unload_scripts_domain();
-#ifdef TOOLS_ENABLED
- Error _load_tools_domain();
-#endif
-
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
uint64_t api_editor_hash;
@@ -170,6 +159,7 @@ public:
#ifdef TOOLS_ENABLED
void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated);
bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type);
+ String get_invalidated_api_assembly_path(APIAssembly::Type p_api_type);
#endif
static GDMono *get_singleton() { return singleton; }
@@ -185,16 +175,14 @@ public:
_FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; }
_FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; }
-#ifdef TOOLS_ENABLED
- _FORCE_INLINE_ MonoDomain *get_tools_domain() { return tools_domain; }
-#endif
_FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; }
#ifdef TOOLS_ENABLED
_FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; }
- _FORCE_INLINE_ GDMonoAssembly *get_editor_tools_assembly() const { return editor_tools_assembly; }
+ _FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; }
+ _FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; }
#endif
#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
@@ -202,6 +190,7 @@ public:
#endif
GDMonoClass *get_class(MonoClass *p_raw_class);
+ GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
#ifdef GD_MONO_HOT_RELOAD
Error reload_scripts_domain();
@@ -267,7 +256,7 @@ public:
(void)__gdmono__scope__exit__domain__unload__;
class _GodotSharp : public Object {
- GDCLASS(_GodotSharp, Object)
+ GDCLASS(_GodotSharp, Object);
friend class GDMono;
@@ -276,6 +265,8 @@ class _GodotSharp : public Object {
List<NodePath *> np_delete_queue;
List<RID *> rid_delete_queue;
+ void _reload_assemblies(bool p_soft_reload);
+
protected:
static _GodotSharp *singleton;
static void _bind_methods();
diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp
index 8fec28b186..8e63ef3563 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -46,11 +46,31 @@ bool GDMonoAssembly::in_preload = false;
Vector<String> GDMonoAssembly::search_dirs;
-void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config) {
+static String _get_expected_api_build_config() {
+#ifdef TOOLS_ENABLED
+ return "Debug";
+#else
+
+#ifdef DEBUG_ENABLED
+ return "Debug";
+#else
+ return "Release";
+#endif
+
+#endif
+}
+
+void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) {
+
+ String framework_dir;
- const char *rootdir = mono_assembly_getrootdir();
- if (rootdir) {
- String framework_dir = String::utf8(rootdir).plus_file("mono").plus_file("4.5");
+ if (!p_custom_bcl_dir.empty()) {
+ framework_dir = p_custom_bcl_dir;
+ } else if (mono_assembly_getrootdir()) {
+ framework_dir = String::utf8(mono_assembly_getrootdir()).plus_file("mono").plus_file("4.5");
+ }
+
+ if (!framework_dir.empty()) {
r_search_dirs.push_back(framework_dir);
r_search_dirs.push_back(framework_dir.plus_file("Facades"));
}
@@ -61,11 +81,19 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
}
+ String api_config = p_custom_config.empty() ? _get_expected_api_build_config() :
+ (p_custom_config == "Release" ? "Release" : "Debug");
+ r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config));
+
r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir());
r_search_dirs.push_back(OS::get_singleton()->get_resource_dir());
r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir());
+
#ifdef TOOLS_ENABLED
r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir());
+
+ // For GodotTools to find the api assemblies
+ r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"));
#endif
}
@@ -264,7 +292,18 @@ Error GDMonoAssembly::load(bool p_refonly) {
Vector<uint8_t> data = FileAccess::get_file_as_array(path);
ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ);
- String image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+ String image_filename;
+
+#ifdef ANDROID_ENABLED
+ if (path.begins_with("res://")) {
+ image_filename = path.substr(6, path.length());
+ } else {
+ image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+ }
+#else
+ // FIXME: globalize_path does not work on exported games
+ image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+#endif
MonoImageOpenStatus status = MONO_IMAGE_OK;
diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h
index 32432af37d..39749dfc1d 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.h
+++ b/modules/mono/mono_gd/gd_mono_assembly.h
@@ -122,7 +122,7 @@ public:
GDMonoClass *get_object_derived_class(const StringName &p_class);
- static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String());
+ static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());
static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp
index 4342f46109..1c10d3c8eb 100644
--- a/modules/mono/mono_gd/gd_mono_class.cpp
+++ b/modules/mono/mono_gd/gd_mono_class.cpp
@@ -41,7 +41,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) {
MonoException *exc = NULL;
MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ UNHANDLED_EXCEPTION(exc);
return GDMonoMarshal::mono_string_to_godot(str);
}
@@ -74,16 +74,13 @@ bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
}
GDMonoClass *GDMonoClass::get_parent_class() {
+ MonoClass *parent_mono_class = mono_class_get_parent(mono_class);
+ return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : NULL;
+}
- if (assembly) {
- MonoClass *parent_mono_class = mono_class_get_parent(mono_class);
-
- if (parent_mono_class) {
- return GDMono::get_singleton()->get_class(parent_mono_class);
- }
- }
-
- return NULL;
+GDMonoClass *GDMonoClass::get_nesting_class() {
+ MonoClass *nesting_type = mono_class_get_nesting_type(mono_class);
+ return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : NULL;
}
#ifdef TOOLS_ENABLED
diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h
index 249422b844..40e1574927 100644
--- a/modules/mono/mono_gd/gd_mono_class.h
+++ b/modules/mono/mono_gd/gd_mono_class.h
@@ -121,6 +121,7 @@ public:
_FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; }
GDMonoClass *get_parent_class();
+ GDMonoClass *get_nesting_class();
#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 9779797d1a..3999658f93 100644
--- a/modules/mono/mono_gd/gd_mono_field.cpp
+++ b/modules/mono/mono_gd/gd_mono_field.cpp
@@ -313,18 +313,44 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
break;
}
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
+
+ MonoReflectionType *key_reftype, *value_reftype;
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(),
+ GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
mono_field_set_value(p_object, mono_field, managed);
break;
}
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(),
+ GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
mono_field_set_value(p_object, mono_field, managed);
break;
}
+ if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
+ if (GDMonoUtils::tools_godot_api_check()) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ } else {
+ MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+ }
+
ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name());
ERR_FAIL();
} break;
@@ -430,28 +456,26 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
} break;
case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
-
- MonoException *exc = NULL;
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
- GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary);
- MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- if (is_dict) {
+ if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class);
mono_field_set_value(p_object, mono_field, managed);
break;
}
- exc = NULL;
+ if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class);
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
- GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray);
- MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
- if (is_array) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class);
+ MonoReflectionType *key_reftype, *value_reftype;
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(),
+ GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
mono_field_set_value(p_object, mono_field, managed);
break;
}
@@ -462,11 +486,25 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
break;
}
- if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(),
+ GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
mono_field_set_value(p_object, mono_field, managed);
break;
}
+
+ if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
+ if (GDMonoUtils::tools_godot_api_check()) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ } else {
+ MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+ }
} break;
default: {
diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h
index e348583370..a7727ddf34 100644
--- a/modules/mono/mono_gd/gd_mono_field.h
+++ b/modules/mono/mono_gd/gd_mono_field.h
@@ -47,9 +47,11 @@ class GDMonoField : public IMonoClassMember {
MonoCustomAttrInfo *attributes;
public:
- virtual MemberType get_member_type() GD_FINAL { return MEMBER_TYPE_FIELD; }
+ virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; }
- virtual StringName get_name() GD_FINAL { return name; }
+ virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_FIELD; }
+
+ virtual StringName get_name() const GD_FINAL { return name; }
virtual bool is_static() GD_FINAL;
virtual Visibility get_visibility() GD_FINAL;
diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp
index 63bcfe053c..a84332d4cd 100644
--- a/modules/mono/mono_gd/gd_mono_internals.cpp
+++ b/modules/mono/mono_gd/gd_mono_internals.cpp
@@ -74,15 +74,14 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass);
script_binding.wrapper_class = klass;
script_binding.gchandle = MonoGCHandle::create_strong(managed);
+ script_binding.owner = unmanaged;
- Reference *kref = Object::cast_to<Reference>(unmanaged);
- if (kref) {
+ if (ref) {
// Unsafe refcount increment. The managed instance also counts as a reference.
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
-
- kref->reference();
+ ref->reference();
}
// The object was just created, no script instance binding should have been attached
@@ -105,8 +104,6 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
ScriptInstance *si = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle);
unmanaged->set_script_and_instance(script.get_ref_ptr(), si);
-
- return;
}
void unhandled_exception(MonoException *p_exc) {
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index 3191cdbd53..a6e04e561d 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -37,6 +37,7 @@
#include "core/os/os.h"
#include "../godotsharp_dirs.h"
+#include "../utils/string_utils.h"
static int log_level_get_id(const char *p_log_level) {
@@ -125,27 +126,6 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
da->list_dir_end();
}
-static String format(const char *p_fmt, ...) {
- va_list args;
-
- va_start(args, p_fmt);
- int len = vsnprintf(NULL, 0, p_fmt, args);
- va_end(args);
-
- len += 1; // for the trailing '/0'
-
- char *buffer(memnew_arr(char, len));
-
- va_start(args, p_fmt);
- vsnprintf(buffer, len, p_fmt, args);
- va_end(args);
-
- String res(buffer);
- memdelete_arr(buffer);
-
- return res;
-}
-
void GDMonoLog::initialize() {
CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8();
@@ -172,7 +152,7 @@ void GDMonoLog::initialize() {
OS::Time time_now = OS::get_singleton()->get_time();
int pid = OS::get_singleton()->get_process_id();
- String log_file_name = format("%d-%02d-%02d %02d:%02d:%02d (%d).txt",
+ String log_file_name = str_format("%d_%02d_%02d %02d.%02d.%02d (%d).txt",
date_now.year, date_now.month, date_now.day,
time_now.hour, time_now.min, time_now.sec, pid);
diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp
index de4f3650bd..42102ed835 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.cpp
+++ b/modules/mono/mono_gd/gd_mono_marshal.cpp
@@ -35,7 +35,7 @@
namespace GDMonoMarshal {
-Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_export_info) {
+Variant::Type managed_to_variant_type(const ManagedType &p_type) {
switch (p_type.type_encoding) {
case MONO_TYPE_BOOLEAN:
return Variant::BOOL;
@@ -157,64 +157,50 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_e
return Variant::ARRAY;
}
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
+
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
+ return Variant::DICTIONARY;
+ }
+
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
return Variant::DICTIONARY;
}
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) {
+ return Variant::ARRAY;
+ }
+
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
return Variant::ARRAY;
}
} break;
case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
-
- MonoException *exc = NULL;
- GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary);
- MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- if (is_dict) {
- if (r_export_info) {
- MonoReflectionType *key_reftype;
- MonoReflectionType *value_reftype;
-
- exc = NULL;
- invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes),
- reftype, &key_reftype, &value_reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- r_export_info->dictionary.key_type = managed_to_variant_type(ManagedType::from_reftype(key_reftype));
- r_export_info->dictionary.value_type = managed_to_variant_type(ManagedType::from_reftype(value_reftype));
- }
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
+ if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
return Variant::DICTIONARY;
}
- exc = NULL;
- GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray);
- MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- if (is_array) {
- if (r_export_info) {
- MonoReflectionType *elem_reftype;
-
- exc = NULL;
- invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType),
- reftype, &elem_reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- r_export_info->array.element_type = managed_to_variant_type(ManagedType::from_reftype(elem_reftype));
- }
-
+ if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
return Variant::ARRAY;
}
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype))
+ return Variant::DICTIONARY;
+
if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
return Variant::DICTIONARY;
}
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype))
+ return Variant::ARRAY;
+
if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
return Variant::ARRAY;
}
@@ -228,6 +214,63 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_e
return Variant::NIL;
}
+bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) {
+ switch (p_array_type.type_encoding) {
+ case MONO_TYPE_GENERICINST: {
+ MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type());
+
+ if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype)) {
+ MonoReflectionType *elem_reftype;
+
+ GDMonoUtils::Marshal::array_get_element_type(array_reftype, &elem_reftype);
+
+ r_elem_type = ManagedType::from_reftype(elem_reftype);
+ return true;
+ }
+
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(array_reftype, &elem_reftype)) {
+ r_elem_type = ManagedType::from_reftype(elem_reftype);
+ return true;
+ }
+ } break;
+ default: {
+ } break;
+ }
+
+ 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)) {
+ 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;
+ }
+
+ MonoReflectionType *key_reftype, *value_reftype;
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(dict_reftype, &key_reftype, &value_reftype)) {
+ r_key_type = ManagedType::from_reftype(key_reftype);
+ r_value_type = ManagedType::from_reftype(value_reftype);
+ return true;
+ }
+ } break;
+ default: {
+ } break;
+ }
+
+ return false;
+}
+
String mono_to_utf8_string(MonoString *p_mono_string) {
MonoError error;
char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error);
@@ -494,12 +537,32 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
}
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
+
+ MonoReflectionType *key_reftype, *value_reftype;
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
+ return GDMonoUtils::create_managed_from(p_var->operator Dictionary(),
+ GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
+ }
+
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
}
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
+ return GDMonoUtils::create_managed_from(p_var->operator Array(),
+ GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
+ }
+
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
+ if (GDMonoUtils::tools_godot_api_check()) {
+ return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
+ } else {
+ return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
+ }
}
} break;
case MONO_TYPE_OBJECT: {
@@ -593,32 +656,40 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
break;
case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
-
- MonoException *exc = NULL;
- GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary);
- MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
- if (is_dict) {
+ if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class);
}
- exc = NULL;
- GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray);
- MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- if (is_array) {
+ if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), p_type.type_class);
}
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ MonoReflectionType *key_reftype, *value_reftype;
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
+ return GDMonoUtils::create_managed_from(p_var->operator Dictionary(),
+ GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
+ }
+
if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
}
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
+ return GDMonoUtils::create_managed_from(p_var->operator Array(),
+ GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
+ }
+
if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
+ if (GDMonoUtils::tools_godot_api_check()) {
+ return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
+ } else {
+ return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
+ }
}
} break;
} break;
@@ -768,77 +839,71 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
if (CACHED_CLASS(Array) == type_class) {
MonoException *exc = NULL;
Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
if (CACHED_CLASS(Dictionary) == type_class) {
MonoException *exc = NULL;
Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
+
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
+ return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj);
+ }
+
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- Dictionary dict;
- MonoException *exc = NULL;
- invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary), p_obj, &dict, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
- return dict;
+ return GDMonoUtils::Marshal::idictionary_to_dictionary(p_obj);
+ }
+
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) {
+ return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
}
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- Array array;
- MonoException *exc = NULL;
- invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray), p_obj, &array, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
- return array;
+ return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
}
} break;
case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
-
- MonoException *exc = NULL;
-
- GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary);
- MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
- if (is_dict) {
- exc = NULL;
+ if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
+ MonoException *exc = NULL;
MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ UNHANDLED_EXCEPTION(exc);
return *unbox<Dictionary *>(ret);
}
- exc = NULL;
-
- GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray);
- MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- if (is_array) {
- exc = NULL;
+ if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
+ MonoException *exc = NULL;
MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ UNHANDLED_EXCEPTION(exc);
return *unbox<Array *>(ret);
}
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
+ return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj);
+ }
+
if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- Dictionary dict;
- exc = NULL;
- invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary), p_obj, &dict, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
- return dict;
+ return GDMonoUtils::Marshal::idictionary_to_dictionary(p_obj);
+ }
+
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) {
+ return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
}
if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- Array array;
- exc = NULL;
- invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray), p_obj, &array, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
- return array;
+ return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
}
} break;
}
@@ -962,7 +1027,7 @@ MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) {
for (int i = 0; i < p_array.size(); i++) {
MonoString *boxed = mono_string_from_godot(r[i]);
- mono_array_set(ret, MonoString *, i, boxed);
+ mono_array_setref(ret, i, boxed);
}
return ret;
@@ -1067,4 +1132,5 @@ PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) {
return ret;
}
+
} // namespace GDMonoMarshal
diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h
index 4a73f9e3e6..3fa958ac32 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.h
+++ b/modules/mono/mono_gd/gd_mono_marshal.h
@@ -57,25 +57,10 @@ T unbox(MonoObject *p_obj) {
#define BOX_PTR(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(IntPtr), x)
#define BOX_ENUM(m_enum_class, x) mono_value_box(mono_domain_get(), m_enum_class, &x)
-// FIXME: Made this struct in a hurry. It could be done differently.
-struct ExportInfo {
- struct ArrayInfo {
- Variant::Type element_type;
-
- ArrayInfo() :
- element_type(Variant::NIL) {}
- } array;
- struct DictionaryInfo {
- Variant::Type key_type;
- Variant::Type value_type;
-
- DictionaryInfo() :
- key_type(Variant::NIL),
- value_type(Variant::NIL) {}
- } dictionary;
-};
+Variant::Type managed_to_variant_type(const ManagedType &p_type);
-Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_export_info = NULL);
+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
diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp
index 7f11e4671d..968b316a3e 100644
--- a/modules/mono/mono_gd/gd_mono_method.cpp
+++ b/modules/mono/mono_gd/gd_mono_method.cpp
@@ -74,6 +74,10 @@ void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) {
method_info = MethodInfo();
}
+GDMonoClass *GDMonoMethod::get_enclosing_class() const {
+ return GDMono::get_singleton()->get_class(mono_method_get_class(mono_method));
+}
+
bool GDMonoMethod::is_static() {
return mono_method_get_flags(mono_method, NULL) & MONO_METHOD_ATTR_STATIC;
}
@@ -105,7 +109,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params,
for (int i = 0; i < params_count; i++) {
MonoObject *boxed_param = GDMonoMarshal::variant_to_mono_object(p_params[i], param_types[i]);
- mono_array_set(params, MonoObject *, i, boxed_param);
+ mono_array_setref(params, i, boxed_param);
}
MonoException *exc = NULL;
diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h
index f74cef438d..2fc8628f27 100644
--- a/modules/mono/mono_gd/gd_mono_method.h
+++ b/modules/mono/mono_gd/gd_mono_method.h
@@ -57,9 +57,11 @@ class GDMonoMethod : public IMonoClassMember {
MonoMethod *mono_method;
public:
- virtual MemberType get_member_type() GD_FINAL { return MEMBER_TYPE_METHOD; }
+ virtual GDMonoClass *get_enclosing_class() const GD_FINAL;
- virtual StringName get_name() GD_FINAL { return name; }
+ virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_METHOD; }
+
+ virtual StringName get_name() const GD_FINAL { return name; }
virtual bool is_static() GD_FINAL;
diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp
index 5842e26241..f1da00638f 100644
--- a/modules/mono/mono_gd/gd_mono_property.cpp
+++ b/modules/mono/mono_gd/gd_mono_property.cpp
@@ -142,7 +142,7 @@ bool GDMonoProperty::has_setter() {
void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) {
MonoMethod *prop_method = mono_property_get_set_method(mono_property);
MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1);
- mono_array_set(params, MonoObject *, 0, p_value);
+ mono_array_setref(params, 0, p_value);
MonoException *exc = NULL;
GDMonoUtils::runtime_invoke_array(prop_method, p_object, params, &exc);
if (exc) {
diff --git a/modules/mono/mono_gd/gd_mono_property.h b/modules/mono/mono_gd/gd_mono_property.h
index 2700c460b0..d6efa60412 100644
--- a/modules/mono/mono_gd/gd_mono_property.h
+++ b/modules/mono/mono_gd/gd_mono_property.h
@@ -47,9 +47,11 @@ class GDMonoProperty : public IMonoClassMember {
MonoCustomAttrInfo *attributes;
public:
- virtual MemberType get_member_type() GD_FINAL { return MEMBER_TYPE_PROPERTY; }
+ virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; }
- virtual StringName get_name() GD_FINAL { return name; }
+ virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_PROPERTY; }
+
+ virtual StringName get_name() const GD_FINAL { return name; }
virtual bool is_static() GD_FINAL;
virtual Visibility get_visibility() GD_FINAL;
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index bcf5712d16..5987fa8ebb 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -50,6 +50,7 @@ MonoCache mono_cache;
#define CACHE_AND_CHECK(m_var, m_val) \
{ \
+ CRASH_COND(m_var != NULL); \
m_var = m_val; \
if (!m_var) { \
ERR_EXPLAIN("Mono Cache: Member " #m_var " is null"); \
@@ -65,7 +66,9 @@ MonoCache mono_cache;
#define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method, m_val)
#define CACHE_PROPERTY_AND_CHECK(m_class, m_property, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.property_##m_class##_##m_property, m_val)
-void MonoCache::clear_members() {
+void MonoCache::clear_corlib_cache() {
+
+ corlib_cache_updated = false;
class_MonoObject = NULL;
class_bool = NULL;
@@ -93,6 +96,11 @@ void MonoCache::clear_members() {
#endif
class_KeyNotFoundException = NULL;
+}
+
+void MonoCache::clear_godot_api_cache() {
+
+ godot_api_cache_updated = false;
rawclass_Dictionary = NULL;
@@ -109,7 +117,7 @@ void MonoCache::clear_members() {
class_NodePath = NULL;
class_RID = NULL;
class_GodotObject = NULL;
- class_GodotReference = NULL;
+ class_GodotResource = NULL;
class_Node = NULL;
class_Control = NULL;
class_Spatial = NULL;
@@ -117,6 +125,7 @@ void MonoCache::clear_members() {
class_Array = NULL;
class_Dictionary = NULL;
class_MarshalUtils = NULL;
+ class_ISerializationListener = NULL;
#ifdef DEBUG_ENABLED
class_DebuggingUtils = NULL;
@@ -151,20 +160,29 @@ void MonoCache::clear_members() {
methodthunk_SignalAwaiter_FailureCallback = NULL;
methodthunk_GodotTaskScheduler_Activate = NULL;
+ // Start of MarshalUtils methods
+
methodthunk_MarshalUtils_TypeIsGenericArray = NULL;
methodthunk_MarshalUtils_TypeIsGenericDictionary = NULL;
+
methodthunk_MarshalUtils_ArrayGetElementType = NULL;
methodthunk_MarshalUtils_DictionaryGetKeyValueTypes = NULL;
+
+ methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType = NULL;
+ methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType = NULL;
+ methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info = NULL;
+ methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info = NULL;
+
+ methodthunk_MarshalUtils_MakeGenericArrayType = NULL;
+ methodthunk_MarshalUtils_MakeGenericDictionaryType = NULL;
+
methodthunk_MarshalUtils_EnumerableToArray = NULL;
methodthunk_MarshalUtils_IDictionaryToDictionary = NULL;
+ methodthunk_MarshalUtils_GenericIDictionaryToDictionary = NULL;
- task_scheduler_handle = Ref<MonoGCHandle>();
-}
-
-void MonoCache::cleanup() {
+ // End of MarshalUtils methods
- corlib_cache_updated = false;
- godot_api_cache_updated = false;
+ task_scheduler_handle = Ref<MonoGCHandle>();
}
#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class))
@@ -217,7 +235,7 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath));
CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(RID));
CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object));
- CACHE_CLASS_AND_CHECK(GodotReference, GODOT_API_CLASS(Reference));
+ CACHE_CLASS_AND_CHECK(GodotResource, GODOT_API_CLASS(Resource));
CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node));
CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control));
CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial));
@@ -225,6 +243,7 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array));
CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary));
CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils));
+ CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener));
#ifdef DEBUG_ENABLED
CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils));
@@ -258,30 +277,40 @@ void update_godot_api_cache() {
CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method_thunk("FailureCallback", 0));
CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method_thunk("Activate", 0));
+ // Start of MarshalUtils methods
+
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericArray, (TypeIsGenericArray)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("TypeIsGenericArray", 1));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericDictionary, (TypeIsGenericDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("TypeIsGenericDictionary", 1));
+
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArrayGetElementType, (ArrayGetElementType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("ArrayGetElementType", 2));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryGetKeyValueTypes, (DictionaryGetKeyValueTypes)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("DictionaryGetKeyValueTypes", 3));
+
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType, (GenericIEnumerableIsAssignableFromType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIEnumerableIsAssignableFromType", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType, (GenericIDictionaryIsAssignableFromType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryIsAssignableFromType", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info, (GenericIEnumerableIsAssignableFromType_with_info)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIEnumerableIsAssignableFromType", 2));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info, (GenericIDictionaryIsAssignableFromType_with_info)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryIsAssignableFromType", 3));
+
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericArrayType, (MakeGenericArrayType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("MakeGenericArrayType", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericDictionaryType, (MakeGenericDictionaryType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("MakeGenericDictionaryType", 2));
+
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, EnumerableToArray, (EnumerableToArray)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("EnumerableToArray", 2));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IDictionaryToDictionary, (IDictionaryToDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IDictionaryToDictionary", 2));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryToDictionary, (GenericIDictionaryToDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryToDictionary", 2));
+
+ // End of MarshalUtils methods
#ifdef DEBUG_ENABLED
CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method_thunk("GetStackFrameInfo", 4));
#endif
// TODO Move to CSharpLanguage::init() and do handle disposal
- MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
- GDMonoUtils::runtime_object_init(task_scheduler);
+ MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
+ GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler));
mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
mono_cache.godot_api_cache_updated = true;
}
-void clear_cache() {
- mono_cache.cleanup();
- mono_cache.clear_members();
-}
-
MonoObject *unmanaged_get_managed(Object *unmanaged) {
if (!unmanaged)
@@ -344,7 +373,6 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
-
ref->reference();
}
@@ -357,7 +385,7 @@ void set_main_thread(MonoThread *p_thread) {
void attach_current_thread() {
ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized());
- MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN);
+ MonoThread *mono_thread = mono_thread_attach(mono_domain_get());
ERR_FAIL_NULL(mono_thread);
}
@@ -372,11 +400,10 @@ MonoThread *get_current_thread() {
return mono_thread_current();
}
-void runtime_object_init(MonoObject *p_this_obj) {
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- // FIXME: Do not use mono_runtime_object_init, it aborts if an exception is thrown
- mono_runtime_object_init(p_this_obj);
- GD_MONO_END_RUNTIME_INVOKE;
+void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) {
+ GDMonoMethod *ctor = p_class->get_method(".ctor", 0);
+ ERR_FAIL_NULL(ctor);
+ ctor->invoke_raw(p_this_obj, NULL, r_exc);
}
GDMonoClass *get_object_class(MonoObject *p_object) {
@@ -422,33 +449,28 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) {
}
MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) {
- String object_type = p_object->get_class_name();
-
- if (object_type[0] == '_')
- object_type = object_type.substr(1, object_type.length());
-
- if (!ClassDB::is_parent_class(object_type, p_native)) {
+ if (!ClassDB::is_parent_class(p_object->get_class_name(), p_native)) {
ERR_EXPLAIN("Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'");
ERR_FAIL_V(NULL);
}
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object);
// Construct
- GDMonoUtils::runtime_object_init(mono_object);
+ GDMonoUtils::runtime_object_init(mono_object, p_class);
return mono_object;
}
MonoObject *create_managed_from(const NodePath &p_from) {
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(NodePath));
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath));
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
- GDMonoUtils::runtime_object_init(mono_object);
+ GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(NodePath));
CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from)));
@@ -456,11 +478,11 @@ MonoObject *create_managed_from(const NodePath &p_from) {
}
MonoObject *create_managed_from(const RID &p_from) {
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(RID));
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID));
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
- GDMonoUtils::runtime_object_init(mono_object);
+ GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(RID));
CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from)));
@@ -468,7 +490,7 @@ MonoObject *create_managed_from(const RID &p_from) {
}
MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
// Search constructor that takes a pointer as parameter
@@ -492,13 +514,13 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
MonoException *exc = NULL;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ UNHANDLED_EXCEPTION(exc);
return mono_object;
}
MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) {
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
// Search constructor that takes a pointer as parameter
@@ -522,7 +544,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
MonoException *exc = NULL;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ UNHANDLED_EXCEPTION(exc);
return mono_object;
}
@@ -641,7 +663,10 @@ void print_unhandled_exception(MonoException *p_exc) {
}
void set_pending_exception(MonoException *p_exc) {
-#ifdef HAS_PENDING_EXCEPTIONS
+#ifdef NO_PENDING_EXCEPTIONS
+ debug_unhandled_exception(p_exc);
+ GD_UNREACHABLE();
+#else
if (get_runtime_invoke_count() == 0) {
debug_unhandled_exception(p_exc);
GD_UNREACHABLE();
@@ -651,9 +676,6 @@ void set_pending_exception(MonoException *p_exc) {
ERR_PRINTS("Exception thrown from managed code, but it could not be set as pending:");
GDMonoUtils::debug_print_unhandled_exception(p_exc);
}
-#else
- debug_unhandled_exception(p_exc);
- GD_UNREACHABLE();
#endif
}
@@ -727,4 +749,139 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc) {
invoke_method_thunk(CACHED_METHOD_THUNK(GodotObject, Dispose), p_mono_object, r_exc);
}
+namespace Marshal {
+
+#ifdef MONO_GLUE_ENABLED
+#ifdef TOOLS_ENABLED
+#define NO_GLUE_RET(m_ret) \
+ { \
+ if (!mono_cache.godot_api_cache_updated) return m_ret; \
+ }
+#else
+#define NO_GLUE_RET(m_ret) \
+ {}
+#endif
+#else
+#define NO_GLUE_RET(m_ret) \
+ { return m_ret; }
+#endif
+
+bool type_is_generic_array(MonoReflectionType *p_reftype) {
+ NO_GLUE_RET(false);
+ TypeIsGenericArray thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray);
+ MonoException *exc = NULL;
+ MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return (bool)res;
+}
+
+bool type_is_generic_dictionary(MonoReflectionType *p_reftype) {
+ NO_GLUE_RET(false);
+ TypeIsGenericDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary);
+ MonoException *exc = NULL;
+ MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return (bool)res;
+}
+
+void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) {
+ ArrayGetElementType thunk = CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType);
+ MonoException *exc = NULL;
+ invoke_method_thunk(thunk, p_array_reftype, r_elem_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+}
+
+void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
+ DictionaryGetKeyValueTypes thunk = CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes);
+ MonoException *exc = NULL;
+ invoke_method_thunk(thunk, p_dict_reftype, r_key_reftype, r_value_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+}
+
+bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) {
+ NO_GLUE_RET(false);
+ GenericIEnumerableIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType);
+ MonoException *exc = NULL;
+ MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return (bool)res;
+}
+
+bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) {
+ NO_GLUE_RET(false);
+ GenericIDictionaryIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType);
+ MonoException *exc = NULL;
+ MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return (bool)res;
+}
+
+bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) {
+ NO_GLUE_RET(false);
+ GenericIEnumerableIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info);
+ MonoException *exc = NULL;
+ MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_elem_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return (bool)res;
+}
+
+bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
+ NO_GLUE_RET(false);
+ GenericIDictionaryIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info);
+ MonoException *exc = NULL;
+ MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_key_reftype, r_value_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return (bool)res;
+}
+
+Array enumerable_to_array(MonoObject *p_enumerable) {
+ NO_GLUE_RET(Array());
+ Array result;
+ EnumerableToArray thunk = CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray);
+ MonoException *exc = NULL;
+ invoke_method_thunk(thunk, p_enumerable, &result, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return result;
+}
+
+Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) {
+ NO_GLUE_RET(Dictionary());
+ Dictionary result;
+ IDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary);
+ MonoException *exc = NULL;
+ invoke_method_thunk(thunk, p_idictionary, &result, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return result;
+}
+
+Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary) {
+ NO_GLUE_RET(Dictionary());
+ Dictionary result;
+ GenericIDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary);
+ MonoException *exc = NULL;
+ invoke_method_thunk(thunk, p_generic_idictionary, &result, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return result;
+}
+
+GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) {
+ NO_GLUE_RET(NULL);
+ MakeGenericArrayType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType);
+ MonoException *exc = NULL;
+ MonoReflectionType *reftype = invoke_method_thunk(thunk, p_elem_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
+}
+
+GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) {
+ NO_GLUE_RET(NULL);
+ MakeGenericDictionaryType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType);
+ MonoException *exc = NULL;
+ MonoReflectionType *reftype = invoke_method_thunk(thunk, p_key_reftype, p_value_reftype, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
+}
+
+} // namespace Marshal
+
} // namespace GDMonoUtils
diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h
index 87610e286c..f535fbb6d0 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -41,7 +41,7 @@
#include "core/object.h"
#include "core/reference.h"
-#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \
+#define UNHANDLED_EXCEPTION(m_exc) \
if (unlikely(m_exc != NULL)) { \
GDMonoUtils::debug_unhandled_exception(m_exc); \
GD_UNREACHABLE(); \
@@ -60,10 +60,45 @@ typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, Mo
typedef MonoBoolean (*TypeIsGenericArray)(MonoReflectionType *, MonoException **);
typedef MonoBoolean (*TypeIsGenericDictionary)(MonoReflectionType *, MonoException **);
-typedef MonoBoolean (*ArrayGetElementType)(MonoReflectionType *, MonoReflectionType **, MonoException **);
-typedef MonoBoolean (*DictionaryGetKeyValueTypes)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **);
+
+typedef void (*ArrayGetElementType)(MonoReflectionType *, MonoReflectionType **, MonoException **);
+typedef void (*DictionaryGetKeyValueTypes)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **);
+
+typedef MonoBoolean (*GenericIEnumerableIsAssignableFromType)(MonoReflectionType *, MonoException **);
+typedef MonoBoolean (*GenericIDictionaryIsAssignableFromType)(MonoReflectionType *, MonoException **);
+typedef MonoBoolean (*GenericIEnumerableIsAssignableFromType_with_info)(MonoReflectionType *, MonoReflectionType **, MonoException **);
+typedef MonoBoolean (*GenericIDictionaryIsAssignableFromType_with_info)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **);
+
+typedef MonoReflectionType *(*MakeGenericArrayType)(MonoReflectionType *, MonoException **);
+typedef MonoReflectionType *(*MakeGenericDictionaryType)(MonoReflectionType *, MonoReflectionType *, MonoException **);
+
typedef void (*EnumerableToArray)(MonoObject *, Array *, MonoException **);
typedef void (*IDictionaryToDictionary)(MonoObject *, Dictionary *, MonoException **);
+typedef void (*GenericIDictionaryToDictionary)(MonoObject *, Dictionary *, MonoException **);
+
+namespace Marshal {
+
+bool type_is_generic_array(MonoReflectionType *p_reftype);
+bool type_is_generic_dictionary(MonoReflectionType *p_reftype);
+
+void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype);
+void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
+
+bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype);
+bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype);
+bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype);
+bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
+
+GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype);
+GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype);
+
+Array enumerable_to_array(MonoObject *p_enumerable);
+Dictionary idictionary_to_dictionary(MonoObject *p_idictionary);
+Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary);
+
+} // namespace Marshal
+
+// End of MarshalUtils methods
struct MonoCache {
@@ -114,7 +149,7 @@ struct MonoCache {
GDMonoClass *class_NodePath;
GDMonoClass *class_RID;
GDMonoClass *class_GodotObject;
- GDMonoClass *class_GodotReference;
+ GDMonoClass *class_GodotResource;
GDMonoClass *class_Node;
GDMonoClass *class_Control;
GDMonoClass *class_Spatial;
@@ -122,6 +157,7 @@ struct MonoCache {
GDMonoClass *class_Array;
GDMonoClass *class_Dictionary;
GDMonoClass *class_MarshalUtils;
+ GDMonoClass *class_ISerializationListener;
#ifdef DEBUG_ENABLED
GDMonoClass *class_DebuggingUtils;
@@ -156,26 +192,39 @@ struct MonoCache {
SignalAwaiter_FailureCallback methodthunk_SignalAwaiter_FailureCallback;
GodotTaskScheduler_Activate methodthunk_GodotTaskScheduler_Activate;
+ // Start of MarshalUtils methods
+
TypeIsGenericArray methodthunk_MarshalUtils_TypeIsGenericArray;
TypeIsGenericDictionary methodthunk_MarshalUtils_TypeIsGenericDictionary;
+
ArrayGetElementType methodthunk_MarshalUtils_ArrayGetElementType;
DictionaryGetKeyValueTypes methodthunk_MarshalUtils_DictionaryGetKeyValueTypes;
+
+ GenericIEnumerableIsAssignableFromType methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType;
+ GenericIDictionaryIsAssignableFromType methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType;
+ GenericIEnumerableIsAssignableFromType_with_info methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info;
+ GenericIDictionaryIsAssignableFromType_with_info methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info;
+
+ MakeGenericArrayType methodthunk_MarshalUtils_MakeGenericArrayType;
+ MakeGenericDictionaryType methodthunk_MarshalUtils_MakeGenericDictionaryType;
+
EnumerableToArray methodthunk_MarshalUtils_EnumerableToArray;
IDictionaryToDictionary methodthunk_MarshalUtils_IDictionaryToDictionary;
+ GenericIDictionaryToDictionary methodthunk_MarshalUtils_GenericIDictionaryToDictionary;
+
+ // End of MarshalUtils methods
Ref<MonoGCHandle> task_scheduler_handle;
bool corlib_cache_updated;
bool godot_api_cache_updated;
- void clear_members();
- void cleanup();
+ void clear_corlib_cache();
+ void clear_godot_api_cache();
MonoCache() {
- corlib_cache_updated = false;
- godot_api_cache_updated = false;
-
- clear_members();
+ clear_corlib_cache();
+ clear_godot_api_cache();
}
};
@@ -183,7 +232,22 @@ extern MonoCache mono_cache;
void update_corlib_cache();
void update_godot_api_cache();
-void clear_cache();
+
+inline void clear_corlib_cache() {
+ mono_cache.clear_corlib_cache();
+}
+
+inline void clear_godot_api_cache() {
+ mono_cache.clear_godot_api_cache();
+}
+
+_FORCE_INLINE_ bool tools_godot_api_check() {
+#ifdef TOOLS_ENABLED
+ return mono_cache.godot_api_cache_updated;
+#else
+ return true; // Assume it's updated if this was called, otherwise it's a bug
+#endif
+}
_FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) {
p_hash ^= p_with_hash + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2);
@@ -205,7 +269,7 @@ _FORCE_INLINE_ bool is_main_thread() {
return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current();
}
-void runtime_object_init(MonoObject *p_this_obj);
+void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = NULL);
GDMonoClass *get_object_class(MonoObject *p_object);
GDMonoClass *type_get_proxy_class(const StringName &p_type);
diff --git a/modules/mono/mono_gd/i_mono_class_member.h b/modules/mono/mono_gd/i_mono_class_member.h
index 553d9edc72..f4de4e3230 100644
--- a/modules/mono/mono_gd/i_mono_class_member.h
+++ b/modules/mono/mono_gd/i_mono_class_member.h
@@ -53,9 +53,11 @@ public:
virtual ~IMonoClassMember() {}
- virtual MemberType get_member_type() = 0;
+ virtual GDMonoClass *get_enclosing_class() const = 0;
- virtual StringName get_name() = 0;
+ virtual MemberType get_member_type() const = 0;
+
+ virtual StringName get_name() const = 0;
virtual bool is_static() = 0;
diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp
index 5d37e8212f..54d73c971f 100644
--- a/modules/mono/signal_awaiter_utils.cpp
+++ b/modules/mono/signal_awaiter_utils.cpp
@@ -91,11 +91,11 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc
set_completed(true);
int signal_argc = p_argcount - 1;
- MonoArray *signal_args = mono_array_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(MonoObject), signal_argc);
+ MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), signal_argc);
for (int i = 0; i < signal_argc; i++) {
MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]);
- mono_array_set(signal_args, MonoObject *, i, boxed);
+ mono_array_setref(signal_args, i, boxed);
}
MonoException *exc = NULL;
diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h
index 098008ded7..4fb3cdb56d 100644
--- a/modules/mono/signal_awaiter_utils.h
+++ b/modules/mono/signal_awaiter_utils.h
@@ -41,7 +41,7 @@ Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p
class SignalAwaiterHandle : public MonoGCHandle {
- GDCLASS(SignalAwaiterHandle, MonoGCHandle)
+ GDCLASS(SignalAwaiterHandle, MonoGCHandle);
bool completed;
diff --git a/modules/mono/editor/monodevelop_instance.cpp b/modules/mono/utils/android_utils.cpp
index 3caa56d1d0..7dd67e3b8e 100644
--- a/modules/mono/editor/monodevelop_instance.cpp
+++ b/modules/mono/utils/android_utils.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* monodevelop_instance.cpp */
+/* android_utils.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,58 +28,41 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "monodevelop_instance.h"
+#include "android_utils.h"
-#include "../mono_gd/gd_mono.h"
-#include "../mono_gd/gd_mono_class.h"
+#ifdef __ANDROID__
-void MonoDevelopInstance::execute(const Vector<String> &p_files) {
+#include "platform/android/thread_jandroid.h"
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
+namespace GDMonoUtils {
+namespace Android {
- ERR_FAIL_NULL(execute_method);
- ERR_FAIL_COND(gc_handle.is_null());
+String get_app_native_lib_dir() {
+ JNIEnv *env = ThreadAndroid::get_env();
- MonoException *exc = NULL;
+ jclass activityThreadClass = env->FindClass("android/app/ActivityThread");
+ jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
+ jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread);
+ jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;");
+ jobject ctx = env->CallObjectMethod(activityThread, getApplication);
- Variant files = p_files;
- const Variant *args[1] = { &files };
- execute_method->invoke(gc_handle->get_target(), args, &exc);
+ jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
+ jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo);
+ jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;");
+ jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField);
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL();
- }
-}
+ String result;
-void MonoDevelopInstance::execute(const String &p_file) {
+ const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
+ if (nativeLibraryDir_utf8) {
+ result.parse_utf8(nativeLibraryDir_utf8);
+ env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8);
+ }
- Vector<String> files;
- files.push_back(p_file);
- execute(files);
+ return result;
}
-MonoDevelopInstance::MonoDevelopInstance(const String &p_solution, EditorId p_editor_id) {
-
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
-
- GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "MonoDevelopInstance");
-
- MonoObject *obj = mono_object_new(TOOLS_DOMAIN, klass->get_mono_ptr());
+} // namespace Android
+} // namespace GDMonoUtils
- GDMonoMethod *ctor = klass->get_method(".ctor", 2);
- MonoException *exc = NULL;
-
- Variant solution = p_solution;
- Variant editor_id = p_editor_id;
- const Variant *args[2] = { &solution, &editor_id };
- ctor->invoke(obj, args, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL();
- }
-
- gc_handle = MonoGCHandle::create_strong(obj);
- execute_method = klass->get_method("Execute", 1);
-}
+#endif // __ANDROID__
diff --git a/modules/mono/editor/mono_build_info.h b/modules/mono/utils/android_utils.h
index b0ae2ed52e..f911c3fdfe 100644
--- a/modules/mono/editor/mono_build_info.h
+++ b/modules/mono/utils/android_utils.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* mono_build_info.h */
+/* android_utils.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,28 +28,21 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef MONO_BUILD_INFO_H
-#define MONO_BUILD_INFO_H
+#ifndef ANDROID_UTILS_H
+#define ANDROID_UTILS_H
-#include "core/ustring.h"
-#include "core/vector.h"
-
-struct MonoBuildInfo {
+#ifdef __ANDROID__
- struct Hasher {
- static uint32_t hash(const MonoBuildInfo &p_key);
- };
+#include "core/ustring.h"
- String solution;
- String configuration;
- Vector<String> custom_props;
+namespace GDMonoUtils {
+namespace Android {
- bool operator==(const MonoBuildInfo &p_b) const;
+String get_app_native_lib_dir();
- String get_log_dirpath();
+} // namespace Android
+} // namespace GDMonoUtils
- MonoBuildInfo();
- MonoBuildInfo(const String &p_solution, const String &p_config);
-};
+#endif // __ANDROID__
-#endif // MONO_BUILD_INFO_H
+#endif // ANDROID_UTILS_H
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index 6e431f51e7..20863b1afe 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -36,16 +36,21 @@
#include "core/project_settings.h"
#ifdef WINDOWS_ENABLED
+#include <windows.h>
+
#define ENV_PATH_SEP ";"
#else
-#define ENV_PATH_SEP ":"
#include <limits.h>
+#include <unistd.h>
+
+#define ENV_PATH_SEP ":"
#endif
#include <stdlib.h>
-String path_which(const String &p_name) {
+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
@@ -55,7 +60,7 @@ String path_which(const String &p_name) {
return String();
for (int i = 0; i < env_path.size(); i++) {
- String p = path_join(env_path[i], p_name);
+ String p = path::join(env_path[i], p_name);
#ifdef WINDOWS_ENABLED
for (int j = 0; j < exts.size(); j++) {
@@ -73,42 +78,96 @@ String path_which(const String &p_name) {
return String();
}
-void fix_path(const String &p_path, String &r_out) {
- r_out = p_path.replace("\\", "/");
+String cwd() {
+#ifdef WINDOWS_ENABLED
+ const DWORD expected_size = ::GetCurrentDirectoryW(0, NULL);
+
+ String buffer;
+ buffer.resize((int)expected_size);
+ if (::GetCurrentDirectoryW(expected_size, buffer.ptrw()) == 0)
+ return ".";
+
+ return buffer.simplify_path();
+#else
+ char buffer[PATH_MAX];
+ if (::getcwd(buffer, sizeof(buffer)) == NULL)
+ return ".";
+
+ String result;
+ if (result.parse_utf8(buffer))
+ return ".";
- while (true) { // in case of using 2 or more slash
- String compare = r_out.replace("//", "/");
- if (r_out == compare)
- break;
- else
- r_out = compare;
+ return result.simplify_path();
+#endif
+}
+
+String abspath(const String &p_path) {
+ if (p_path.is_abs_path()) {
+ return p_path.simplify_path();
+ } else {
+ return path::join(path::cwd(), p_path).simplify_path();
}
}
-bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) {
+String realpath(const String &p_path) {
#ifdef WINDOWS_ENABLED
- CharType ret[_MAX_PATH];
- if (::_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) {
- String abspath = String(ret).replace("\\", "/");
- int pos = abspath.find(":/");
- if (pos != -1) {
- r_abs_path = abspath.substr(pos - 1, abspath.length());
- } else {
- r_abs_path = abspath;
- }
- return true;
- }
-#else
- char *resolved_path = ::realpath(p_existing_path.utf8().get_data(), NULL);
- if (resolved_path) {
- String retstr;
- bool success = !retstr.parse_utf8(resolved_path);
- ::free(resolved_path);
- if (success) {
- r_abs_path = retstr;
- return true;
- }
+ // Open file without read/write access
+ HANDLE hFile = ::CreateFileW(p_path.c_str(), 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return p_path;
+
+ const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, NULL, 0, FILE_NAME_NORMALIZED);
+
+ if (expected_size == 0) {
+ ::CloseHandle(hFile);
+ return p_path;
}
+
+ String buffer;
+ buffer.resize((int)expected_size);
+ ::GetFinalPathNameByHandleW(hFile, buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED);
+
+ ::CloseHandle(hFile);
+ return buffer.simplify_path();
+#elif UNIX_ENABLED
+ char *resolved_path = ::realpath(p_path.utf8().get_data(), NULL);
+
+ if (!resolved_path)
+ return p_path;
+
+ String result;
+ bool parse_ok = result.parse_utf8(resolved_path);
+ ::free(resolved_path);
+
+ if (parse_ok)
+ return p_path;
+
+ return result.simplify_path();
#endif
- return false;
}
+
+String join(const String &p_a, const String &p_b) {
+ if (p_a.empty())
+ return p_b;
+
+ const CharType a_last = p_a[p_a.length() - 1];
+ if ((a_last == '/' || a_last == '\\') ||
+ (p_b.size() > 0 && (p_b[0] == '/' || p_b[0] == '\\'))) {
+ return p_a + p_b;
+ }
+
+ return p_a + "/" + p_b;
+}
+
+String join(const String &p_a, const String &p_b, const String &p_c) {
+ return path::join(path::join(p_a, p_b), p_c);
+}
+
+String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d) {
+ return path::join(path::join(path::join(p_a, p_b), p_c), p_d);
+}
+
+} // namespace path
diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h
index 69edf4deb7..ca25bc09f7 100644
--- a/modules/mono/utils/path_utils.h
+++ b/modules/mono/utils/path_utils.h
@@ -31,24 +31,32 @@
#ifndef PATH_UTILS_H
#define PATH_UTILS_H
+#include "core/string_builder.h"
#include "core/ustring.h"
-_FORCE_INLINE_ String path_join(const String &e1, const String &e2) {
- return e1.plus_file(e2);
-}
+namespace path {
-_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3) {
- return e1.plus_file(e2).plus_file(e3);
-}
+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);
-_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3, const String &e4) {
- return e1.plus_file(e2).plus_file(e3).plus_file(e4);
-}
+String find_executable(const String &p_name);
-String path_which(const String &p_name);
+/// Returns a normalized absolute path to the current working directory
+String cwd();
-void fix_path(const String &p_path, String &r_out);
+/**
+ * Obtains a normalized absolute path to p_path. Symbolic links are
+ * not resolved. The path p_path might not exist in the file system.
+ */
+String abspath(const String &p_path);
-bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path);
+/**
+ * Obtains a normalized path to p_path with symbolic links resolved.
+ * The resulting path might be either a relative or an absolute path.
+ */
+String realpath(const String &p_path);
+
+} // namespace path
#endif // PATH_UTILS_H
diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp
index c390f8b9c2..2b014c2a45 100644
--- a/modules/mono/utils/string_utils.cpp
+++ b/modules/mono/utils/string_utils.cpp
@@ -32,6 +32,9 @@
#include "core/os/file_access.h"
+#include <stdio.h>
+#include <stdlib.h>
+
namespace {
int sfind(const String &p_text, int p_from) {
@@ -41,7 +44,7 @@ int sfind(const String &p_text, int p_from) {
int src_len = 2;
int len = p_text.length();
- if (src_len == 0 || len == 0)
+ if (len == 0)
return -1;
const CharType *src = p_text.c_str();
@@ -63,7 +66,7 @@ int sfind(const String &p_text, int p_from) {
break;
case 1: {
CharType c = src[read_pos];
- found = src[read_pos] == 's' || (c >= '0' || c <= '4');
+ found = src[read_pos] == 's' || (c >= '0' && c <= '4');
break;
}
default:
@@ -184,3 +187,50 @@ Error read_all_file_utf8(const String &p_path, String &r_content) {
r_content = source;
return OK;
}
+
+// TODO: Move to variadic templates once we upgrade to C++11
+
+String str_format(const char *p_format, ...) {
+ va_list list;
+
+ va_start(list, p_format);
+ String res = str_format(p_format, list);
+ va_end(list);
+
+ 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 vsnprintf(m_buffer, m_count, m_format, m_argptr) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_argptr)
+#endif
+
+String str_format(const char *p_format, va_list p_list) {
+ va_list list;
+
+ va_copy(list, p_list);
+ int len = vsnprintf(NULL, 0, p_format, list);
+ va_end(list);
+
+ len += 1; // for the trailing '/0'
+
+ char *buffer(memnew_arr(char, len));
+
+ va_copy(list, p_list);
+ vsnprintf(buffer, len, p_format, list);
+ va_end(list);
+
+ String res(buffer);
+ memdelete_arr(buffer);
+
+ return res;
+}
diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h
index 61765ccfd8..565b9bb644 100644
--- a/modules/mono/utils/string_utils.h
+++ b/modules/mono/utils/string_utils.h
@@ -34,6 +34,8 @@
#include "core/ustring.h"
#include "core/variant.h"
+#include <stdarg.h>
+
String sformat(const String &p_text, const Variant &p1 = Variant(), const Variant &p2 = Variant(), const Variant &p3 = Variant(), const Variant &p4 = Variant(), const Variant &p5 = Variant());
#ifdef TOOLS_ENABLED
@@ -44,4 +46,15 @@ String escape_csharp_keyword(const String &p_name);
Error read_all_file_utf8(const String &p_path, String &r_content);
+#if defined(__GNUC__)
+#define _PRINTF_FORMAT_ATTRIBUTE_1_0 __attribute__((format(printf, 1, 0)))
+#define _PRINTF_FORMAT_ATTRIBUTE_1_2 __attribute__((format(printf, 1, 2)))
+#else
+#define _PRINTF_FORMAT_ATTRIBUTE_1_0
+#define _PRINTF_FORMAT_ATTRIBUTE_1_2
+#endif
+
+String str_format(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_1_2;
+String str_format(const char *p_format, va_list p_list) _PRINTF_FORMAT_ATTRIBUTE_1_0;
+
#endif // STRING_FORMAT_H
diff --git a/modules/mono/utils/thread_local.h b/modules/mono/utils/thread_local.h
index 488cc2619a..e52b6e73ef 100644
--- a/modules/mono/utils/thread_local.h
+++ b/modules/mono/utils/thread_local.h
@@ -76,7 +76,7 @@ struct ThreadLocalStorage {
void *get_value() const;
void set_value(void *p_value) const;
- void alloc(void(_CALLBACK_FUNC_ *p_dest_callback)(void *));
+ void alloc(void(_CALLBACK_FUNC_ *p_destr_callback)(void *));
void free();
private: