summaryrefslogtreecommitdiff
path: root/modules/mono
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono')
-rw-r--r--modules/mono/.gitignore2
-rw-r--r--modules/mono/SCsub269
-rw-r--r--modules/mono/__init__.py0
-rw-r--r--modules/mono/build_scripts/__init__.py0
-rw-r--r--modules/mono/build_scripts/api_solution_build.py66
-rw-r--r--modules/mono/build_scripts/godot_tools_build.py118
-rw-r--r--modules/mono/build_scripts/make_android_mono_config.py52
-rw-r--r--modules/mono/build_scripts/make_cs_compressed_header.py62
-rw-r--r--modules/mono/build_scripts/mono_configure.py407
-rw-r--r--modules/mono/build_scripts/mono_reg_utils.py (renamed from modules/mono/mono_reg_utils.py)16
-rw-r--r--modules/mono/build_scripts/patches/fix-mono-android-tkill.diff70
-rw-r--r--modules/mono/build_scripts/solution_builder.py214
-rw-r--r--modules/mono/build_scripts/tls_configure.py (renamed from modules/mono/tls_configure.py)0
-rw-r--r--modules/mono/class_db_api_json.cpp246
-rw-r--r--modules/mono/class_db_api_json.h (renamed from modules/mono/editor/net_solution.h)38
-rw-r--r--modules/mono/config.py308
-rw-r--r--modules/mono/csharp_script.cpp1741
-rw-r--r--modules/mono/csharp_script.h145
-rw-r--r--modules/mono/doc_classes/@C#.xml4
-rw-r--r--modules/mono/doc_classes/CSharpScript.xml4
-rw-r--r--modules/mono/doc_classes/GodotSharp.xml38
-rw-r--r--modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs430
-rw-r--r--modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs145
-rw-r--r--modules/mono/editor/GodotSharpTools/GodotSharpTools.sln17
-rw-r--r--modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs17
-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.csproj60
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj (renamed from modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj)23
-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.IdeConnection/ConsoleLogger.cs33
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs94
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs219
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs207
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs45
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj53
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs13
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs46
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs88
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.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.csproj64
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs197
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs (renamed from modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs)19
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs (renamed from modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs)77
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs175
-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.config4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.sln41
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs343
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs172
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs186
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs47
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs259
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildTab.cs267
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs127
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs11
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs475
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs197
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj89
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs48
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs166
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs212
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs131
-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/Globals.cs33
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs91
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs114
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs29
-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/NotifyAwaiter.cs64
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs127
-rw-r--r--modules/mono/editor/bindings_generator.cpp2105
-rw-r--r--modules/mono/editor/bindings_generator.h159
-rw-r--r--modules/mono/editor/csharp_project.cpp96
-rw-r--r--modules/mono/editor/csharp_project.h10
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp445
-rw-r--r--modules/mono/editor/editor_internal_calls.h36
-rw-r--r--modules/mono/editor/godotsharp_builds.cpp600
-rw-r--r--modules/mono/editor/godotsharp_builds.h100
-rw-r--r--modules/mono/editor/godotsharp_editor.cpp482
-rw-r--r--modules/mono/editor/godotsharp_editor.h130
-rw-r--r--modules/mono/editor/godotsharp_export.cpp174
-rw-r--r--modules/mono/editor/godotsharp_export.h30
-rw-r--r--modules/mono/editor/mono_bottom_panel.cpp489
-rw-r--r--modules/mono/editor/mono_bottom_panel.h150
-rw-r--r--modules/mono/editor/net_solution.cpp131
-rw-r--r--modules/mono/editor/script_class_parser.cpp676
-rw-r--r--modules/mono/editor/script_class_parser.h110
-rw-r--r--modules/mono/glue/Managed/.gitignore2
-rw-r--r--modules/mono/glue/Managed/Files/AABB.cs22
-rw-r--r--modules/mono/glue/Managed/Files/Array.cs216
-rw-r--r--modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs16
-rw-r--r--modules/mono/glue/Managed/Files/Basis.cs558
-rw-r--r--modules/mono/glue/Managed/Files/Color.cs121
-rw-r--r--modules/mono/glue/Managed/Files/Colors.cs305
-rw-r--r--modules/mono/glue/Managed/Files/DebuggingUtils.cs6
-rw-r--r--modules/mono/glue/Managed/Files/Dictionary.cs284
-rw-r--r--modules/mono/glue/Managed/Files/Dispatcher.cs13
-rw-r--r--modules/mono/glue/Managed/Files/DynamicObject.cs213
-rw-r--r--modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs26
-rw-r--r--modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs4
-rw-r--r--modules/mono/glue/Managed/Files/GD.cs148
-rw-r--r--modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs7
-rw-r--r--modules/mono/glue/Managed/Files/GodotTaskScheduler.cs9
-rw-r--r--modules/mono/glue/Managed/Files/GodotTraceListener.cs37
-rw-r--r--modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs8
-rw-r--r--modules/mono/glue/Managed/Files/MarshalUtils.cs204
-rw-r--r--modules/mono/glue/Managed/Files/Mathf.cs85
-rw-r--r--modules/mono/glue/Managed/Files/MathfEx.cs24
-rw-r--r--modules/mono/glue/Managed/Files/NodePath.cs12
-rw-r--r--modules/mono/glue/Managed/Files/Object.base.cs44
-rw-r--r--modules/mono/glue/Managed/Files/Plane.cs11
-rw-r--r--modules/mono/glue/Managed/Files/Quat.cs48
-rw-r--r--modules/mono/glue/Managed/Files/RID.cs14
-rw-r--r--modules/mono/glue/Managed/Files/Rect2.cs13
-rw-r--r--modules/mono/glue/Managed/Files/StringExtensions.cs77
-rw-r--r--modules/mono/glue/Managed/Files/Transform.cs49
-rw-r--r--modules/mono/glue/Managed/Files/Transform2D.cs191
-rw-r--r--modules/mono/glue/Managed/Files/Vector2.cs97
-rw-r--r--modules/mono/glue/Managed/Files/Vector3.cs97
-rw-r--r--modules/mono/glue/Managed/IgnoredFiles/Node.cs5
-rw-r--r--modules/mono/glue/Managed/IgnoredFiles/Variant.cs11
-rw-r--r--modules/mono/glue/Managed/Managed.csproj7
-rw-r--r--modules/mono/glue/Managed/Properties/AssemblyInfo.cs4
-rw-r--r--modules/mono/glue/arguments_vector.h68
-rw-r--r--modules/mono/glue/base_object_glue.cpp134
-rw-r--r--modules/mono/glue/base_object_glue.h18
-rw-r--r--modules/mono/glue/collections_glue.cpp101
-rw-r--r--modules/mono/glue/collections_glue.h36
-rw-r--r--modules/mono/glue/gd_glue.cpp71
-rw-r--r--modules/mono/glue/gd_glue.h28
-rw-r--r--modules/mono/glue/glue_header.h6
-rw-r--r--modules/mono/glue/nodepath_glue.cpp6
-rw-r--r--modules/mono/glue/nodepath_glue.h4
-rw-r--r--modules/mono/glue/rid_glue.cpp6
-rw-r--r--modules/mono/glue/rid_glue.h4
-rw-r--r--modules/mono/glue/string_glue.cpp16
-rw-r--r--modules/mono/glue/string_glue.h4
-rw-r--r--modules/mono/godotsharp_defs.h10
-rw-r--r--modules/mono/godotsharp_dirs.cpp148
-rw-r--r--modules/mono/godotsharp_dirs.h19
-rw-r--r--modules/mono/icons/icon_c_#.svg5
-rw-r--r--modules/mono/mono_gc_handle.cpp6
-rw-r--r--modules/mono/mono_gc_handle.h6
-rw-r--r--modules/mono/mono_gd/android_mono_config.h (renamed from modules/mono/editor/monodevelop_instance.h)35
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp958
-rw-r--r--modules/mono/mono_gd/gd_mono.h132
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.cpp202
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.h11
-rw-r--r--modules/mono/mono_gd/gd_mono_class.cpp68
-rw-r--r--modules/mono/mono_gd/gd_mono_class.h17
-rw-r--r--modules/mono/mono_gd/gd_mono_field.cpp436
-rw-r--r--modules/mono/mono_gd/gd_mono_field.h22
-rw-r--r--modules/mono/mono_gd/gd_mono_header.h27
-rw-r--r--modules/mono/mono_gd/gd_mono_internals.cpp67
-rw-r--r--modules/mono/mono_gd/gd_mono_internals.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp71
-rw-r--r--modules/mono/mono_gd/gd_mono_log.h5
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.cpp661
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.h300
-rw-r--r--modules/mono/mono_gd/gd_mono_method.cpp51
-rw-r--r--modules/mono/mono_gd/gd_mono_method.h29
-rw-r--r--modules/mono/mono_gd/gd_mono_property.cpp21
-rw-r--r--modules/mono/mono_gd/gd_mono_property.h23
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp417
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h233
-rw-r--r--modules/mono/mono_gd/i_mono_class_member.h (renamed from modules/mono/mono_gd/gd_mono_class_member.h)23
-rw-r--r--modules/mono/mono_gd/managed_type.cpp (renamed from modules/mono/editor/mono_build_info.cpp)44
-rw-r--r--modules/mono/mono_gd/managed_type.h58
-rw-r--r--modules/mono/register_types.cpp23
-rw-r--r--modules/mono/register_types.h4
-rw-r--r--modules/mono/signal_awaiter_utils.cpp24
-rw-r--r--modules/mono/signal_awaiter_utils.h6
-rw-r--r--modules/mono/utils/android_utils.cpp (renamed from modules/mono/editor/monodevelop_instance.cpp)75
-rw-r--r--modules/mono/utils/android_utils.h (renamed from modules/mono/editor/mono_build_info.h)35
-rw-r--r--modules/mono/utils/macros.h55
-rw-r--r--modules/mono/utils/mono_reg_utils.cpp16
-rw-r--r--modules/mono/utils/mono_reg_utils.h4
-rw-r--r--modules/mono/utils/mutex_utils.h67
-rw-r--r--modules/mono/utils/osx_utils.cpp8
-rw-r--r--modules/mono/utils/osx_utils.h5
-rw-r--r--modules/mono/utils/path_utils.cpp131
-rw-r--r--modules/mono/utils/path_utils.h36
-rw-r--r--modules/mono/utils/string_utils.cpp95
-rw-r--r--modules/mono/utils/string_utils.h19
-rw-r--r--modules/mono/utils/thread_local.cpp4
-rw-r--r--modules/mono/utils/thread_local.h30
202 files changed, 17255 insertions, 7300 deletions
diff --git a/modules/mono/.gitignore b/modules/mono/.gitignore
new file mode 100644
index 0000000000..fa6d00cbbb
--- /dev/null
+++ b/modules/mono/.gitignore
@@ -0,0 +1,2 @@
+# Do not ignore solution files inside the mono module. Overrides Godot's global gitignore.
+!*.sln
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index 1d5c145027..a9afa7ccf6 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -1,259 +1,68 @@
#!/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')
env_mono = env_modules.Clone()
-# TODO move functions to their own modules
-
-def make_cs_files_header(src, dst, version_dst):
- from compat import byte_to_str
-
- with open(dst, 'w') as header:
- header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n')
- header.write('#ifndef CS_COMPRESSED_H\n')
- header.write('#define CS_COMPRESSED_H\n\n')
- header.write('#ifdef TOOLS_ENABLED\n\n')
- header.write('#include "core/map.h"\n')
- header.write('#include "core/ustring.h"\n')
- inserted_files = ''
- import os
- latest_mtime = 0
- cs_file_count = 0
- for root, _, files in os.walk(src):
- files = [f for f in files if f.endswith('.cs')]
- for file in files:
- cs_file_count += 1
- filepath = os.path.join(root, file)
- filepath_src_rel = os.path.relpath(filepath, src)
- mtime = os.path.getmtime(filepath)
- latest_mtime = mtime if mtime > latest_mtime else latest_mtime
- with open(filepath, 'rb') as f:
- buf = f.read()
- decomp_size = len(buf)
- import zlib
- buf = zlib.compress(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 unsigned char _cs_' + name + '_compressed[] = { ')
- for i, buf_idx in enumerate(range(len(buf))):
- if i > 0:
- header.write(', ')
- header.write(byte_to_str(buf[buf_idx]))
- inserted_files += '\tr_files.insert("' + filepath_src_rel + '", ' \
- 'CompressedFile(_cs_' + name + '_compressed_size, ' \
- '_cs_' + name + '_uncompressed_size, ' \
- '_cs_' + name + '_compressed));\n'
- header.write(' };\n')
- header.write('\nstruct CompressedFile\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'
- '\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'
- )
- header.write('\n#endif // TOOLS_ENABLED\n')
- header.write('\n#endif // CS_COMPRESSED_H\n')
-
- glue_version = int(latest_mtime) # The latest modified time will do for now
-
- with open(version_dst, 'w') as version_header:
- version_header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n')
- version_header.write('#ifndef CS_GLUE_VERSION_H\n')
- version_header.write('#define CS_GLUE_VERSION_H\n\n')
- version_header.write('#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n')
- version_header.write('\n#endif // CS_GLUE_VERSION_H\n')
-
-
-env_mono.add_source_files(env.modules_sources, '*.cpp')
-env_mono.add_source_files(env.modules_sources, 'glue/*.cpp')
-env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp')
-env_mono.add_source_files(env.modules_sources, 'utils/*.cpp')
-
if env['tools']:
- env_mono.add_source_files(env.modules_sources, 'editor/*.cpp')
# NOTE: It is safe to generate this file here, since this is still executed serially
- make_cs_files_header('glue/Managed/Files', 'glue/cs_compressed.gen.h', '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)
+ import build_scripts.make_cs_compressed_header as make_cs_compressed_header
+ make_cs_compressed_header.generate_header(
+ 'glue/Managed/Files',
+ 'glue/cs_compressed.gen.h',
+ 'glue/cs_glue_version.gen.h'
+ )
# Glue sources
if env_mono['mono_glue']:
env_mono.Append(CPPDEFINES=['MONO_GLUE_ENABLED'])
-if ARGUMENTS.get('yolo_copy', False):
- env_mono.Append(CPPDEFINES=['YOLO_COPY'])
+ import os.path
+ if not os.path.isfile('glue/mono_glue.gen.cpp'):
+ raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
-# Configure TLS checks
+if env_mono['tools'] or env_mono['target'] != 'release':
+ env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD'])
+
+# Configure Thread Local Storage
-import tls_configure
conf = Configure(env_mono)
tls_configure.configure(conf)
env_mono = conf.Finish()
+# Configure Mono
-# Build GodotSharpTools solution
-
-
-import os
-
-
-def find_msbuild_unix(filename):
- import os.path
- import sys
-
- hint_dirs = ['/opt/novell/mono/bin']
- if sys.platform == 'darwin':
- hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs
-
- for hint_dir in hint_dirs:
- hint_path = os.path.join(hint_dir, filename)
- if os.path.isfile(hint_path):
- return hint_path
- elif os.path.isfile(hint_path + '.exe'):
- return hint_path + '.exe'
-
- for hint_dir in os.environ['PATH'].split(os.pathsep):
- hint_dir = hint_dir.strip('"')
- hint_path = os.path.join(hint_dir, filename)
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
- if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
- return hint_path + '.exe'
+mono_configure.configure(env, env_mono)
- return None
+# 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)
-def find_msbuild_windows():
- import mono_reg_utils as monoreg
+# Build GodotTools
- 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)
+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:
- if os.getenv('MONO64_PREFIX'):
- mono_root = os.getenv('MONO64_PREFIX')
- else:
- mono_root = monoreg.find_mono_root_dir(bits)
-
- if not mono_root:
- raise RuntimeError('Cannot find mono root directory')
-
- framework_path = os.path.join(mono_root, 'lib', 'mono', '4.5')
- mono_bin_dir = os.path.join(mono_root, 'bin')
- msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat')
-
- if os.path.isfile(msbuild_mono):
- # The (Csc/Vbc/Fsc)ToolExe environment variables are required when
- # building with Mono's MSBuild. They must point to the batch files
- # in Mono's bin directory to make sure they are executed with Mono.
- mono_msbuild_env = {
- 'CscToolExe': os.path.join(mono_bin_dir, 'csc.bat'),
- 'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'),
- 'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat')
- }
- return (msbuild_mono, framework_path, mono_msbuild_env)
-
- msbuild_tools_path = monoreg.find_msbuild_tools_path_reg()
+ # 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)
- if msbuild_tools_path:
- return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), framework_path, {})
+# Add sources
- return None
-
-
-def mono_build_solution(source, target, env):
- import subprocess
- import mono_reg_utils as monoreg
- from shutil import copyfile
-
- framework_path = ''
-
- msbuild_env = os.environ.copy()
-
- # Needed when running from Developer Command Prompt for VS
- if 'PLATFORM' in msbuild_env:
- del msbuild_env['PLATFORM']
-
- if os.name == 'nt':
- msbuild_info = find_msbuild_windows()
- if msbuild_info is None:
- raise RuntimeError('Cannot find MSBuild executable')
- msbuild_path = msbuild_info[0]
- framework_path = msbuild_info[1]
- msbuild_env.update(msbuild_info[2])
- else:
- msbuild_path = find_msbuild_unix('msbuild')
- if msbuild_path is None:
- xbuild_fallback = env['xbuild_fallback']
-
- if xbuild_fallback and os.name == 'nt':
- print('Option \'xbuild_fallback\' not supported on Windows')
- xbuild_fallback = False
-
- if xbuild_fallback:
- print('Cannot find MSBuild executable, trying with xbuild')
- print('Warning: xbuild is deprecated')
-
- msbuild_path = find_msbuild_unix('xbuild')
-
- if msbuild_path is None:
- raise RuntimeError('Cannot find xbuild executable')
- else:
- raise RuntimeError('Cannot find MSBuild executable')
-
- print('MSBuild path: ' + msbuild_path)
-
- build_config = 'Release'
-
- msbuild_args = [
- msbuild_path,
- os.path.abspath(str(source[0])),
- '/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')
-
- src_dir = os.path.abspath(os.path.join(str(source[0]), os.pardir, 'bin', build_config))
- dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
-
- if not os.path.isdir(dst_dir):
- if os.path.exists(dst_dir):
- raise RuntimeError('Target directory is a file')
- os.makedirs(dst_dir)
-
- asm_file = 'GodotSharpTools.dll'
-
- copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file))
-
-output_dir = Dir('#bin').abspath
-assemblies_output_dir = Dir(env['mono_assemblies_output_dir']).abspath
-
-mono_sln_builder = Builder(action=mono_build_solution)
-env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder})
-env_mono.MonoBuildSolution(
- os.path.join(assemblies_output_dir, 'GodotSharpTools.dll'),
- 'editor/GodotSharpTools/GodotSharpTools.sln'
-)
+env_mono.add_source_files(env.modules_sources, '*.cpp')
+env_mono.add_source_files(env.modules_sources, 'glue/*.cpp')
+env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp')
+env_mono.add_source_files(env.modules_sources, 'utils/*.cpp')
-if os.path.normpath(output_dir) != os.path.normpath(assemblies_output_dir):
- rel_assemblies_output_dir = os.path.relpath(assemblies_output_dir, output_dir)
- env_mono.Append(CPPDEFINES={'GD_MONO_EDITOR_ASSEMBLIES_DIR': rel_assemblies_output_dir})
+if env['tools']:
+ env_mono.add_source_files(env.modules_sources, 'editor/*.cpp')
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/__init__.py b/modules/mono/build_scripts/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/mono/build_scripts/__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..35daa6d307
--- /dev/null
+++ b/modules/mono/build_scripts/godot_tools_build.py
@@ -0,0 +1,118 @@
+# 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.IdeConnection.dll', 'GodotTools.BuildLogger.dll',
+ 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'
+ ]
+
+ if env_mono['target'] == 'debug':
+ target_filenames += [
+ 'GodotTools.pdb', 'GodotTools.IdeConnection.pdb', 'GodotTools.BuildLogger.pdb',
+ 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'
+ ]
+
+ 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']
+
+ if env_mono['target'] == 'debug':
+ target_filenames += ['GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb']
+
+ 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..8cad204d7b
--- /dev/null
+++ b/modules/mono/build_scripts/make_android_mono_config.py
@@ -0,0 +1,52 @@
+
+def generate_compressed_config(config_src, output_dir):
+ import os.path
+ from compat import byte_to_str
+
+ # Source file
+ with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp:
+ with open(config_src, 'rb') as f:
+ 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.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
new file mode 100644
index 0000000000..ed49db5bb2
--- /dev/null
+++ b/modules/mono/build_scripts/make_cs_compressed_header.py
@@ -0,0 +1,62 @@
+
+def generate_header(src, dst, version_dst):
+ from compat import byte_to_str
+
+ with open(dst, 'w') as header:
+ header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n')
+ header.write('#ifndef CS_COMPRESSED_H\n')
+ header.write('#define CS_COMPRESSED_H\n\n')
+ header.write('#ifdef TOOLS_ENABLED\n\n')
+ header.write('#include "core/map.h"\n')
+ header.write('#include "core/ustring.h"\n')
+ inserted_files = ''
+ import os
+ latest_mtime = 0
+ cs_file_count = 0
+ for root, _, files in os.walk(src):
+ files = [f for f in files if f.endswith('.cs')]
+ for file in files:
+ cs_file_count += 1
+ filepath = os.path.join(root, file)
+ filepath_src_rel = os.path.relpath(filepath, src)
+ mtime = os.path.getmtime(filepath)
+ latest_mtime = mtime if mtime > latest_mtime else latest_mtime
+ with open(filepath, 'rb') as f:
+ buf = f.read()
+ 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(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(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('\\', '\\\\') + '", ' \
+ 'GodotCsCompressedFile(_cs_' + name + '_compressed_size, ' \
+ '_cs_' + name + '_uncompressed_size, ' \
+ '_cs_' + name + '_compressed));\n'
+ header.write('\nstruct GodotCsCompressedFile\n' '{\n'
+ '\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* 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\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')
+
+ glue_version = int(latest_mtime) # The latest modified time will do for now
+
+ with open(version_dst, 'w') as version_header:
+ version_header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n')
+ version_header.write('#ifndef CS_GLUE_VERSION_H\n')
+ version_header.write('#define CS_GLUE_VERSION_H\n\n')
+ version_header.write('#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n')
+ version_header.write('\n#endif // CS_GLUE_VERSION_H\n')
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
new file mode 100644
index 0000000000..4c1ebd8d74
--- /dev/null
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -0,0 +1,407 @@
+import os
+import os.path
+import sys
+import subprocess
+
+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/lib/libs').abspath,
+ 'release' if env['target'] == 'release' else 'debug',
+ android_arch_dirs[env['android_arch']])
+
+
+def find_file_in_dir(directory, files, prefix='', extension=''):
+ if not extension.startswith('.'):
+ extension = '.' + extension
+ for curfile in files:
+ if os.path.isfile(os.path.join(directory, prefix + curfile + extension)):
+ return curfile
+ return ''
+
+
+def copy_file(src_dir, dst_dir, name):
+ from shutil import copy
+
+ src_path = os.path.join(Dir(src_dir).abspath, name)
+ dst_dir = Dir(dst_dir).abspath
+
+ if not os.path.isdir(dst_dir):
+ os.makedirs(dst_dir)
+
+ copy(src_path, dst_dir)
+
+
+def configure(env, env_mono):
+ 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_prefix
+
+ if not mono_root and os.name == 'nt':
+ mono_root = monoreg.find_mono_root_dir(bits)
+
+ if not mono_root:
+ raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter")
+
+ print('Found Mono root directory: ' + mono_root)
+
+ mono_lib_path = os.path.join(mono_root, 'lib')
+
+ env.Append(LIBPATH=mono_lib_path)
+ env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
+
+ if mono_static:
+ lib_suffix = Environment()['LIBSUFFIX']
+
+ if env.msvc:
+ mono_static_lib_name = 'libmono-static-sgen'
+ else:
+ mono_static_lib_name = 'libmonosgen-2.0'
+
+ if not os.path.isfile(os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)):
+ raise RuntimeError('Could not find static mono library in: ' + mono_lib_path)
+
+ if env.msvc:
+ env.Append(LINKFLAGS=mono_static_lib_name + lib_suffix)
+
+ env.Append(LINKFLAGS='Mincore' + lib_suffix)
+ env.Append(LINKFLAGS='msvcrt' + lib_suffix)
+ env.Append(LINKFLAGS='LIBCMT' + lib_suffix)
+ env.Append(LINKFLAGS='Psapi' + lib_suffix)
+ else:
+ env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix))
+
+ env.Append(LIBS=['psapi'])
+ env.Append(LIBS=['version'])
+ else:
+ mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib')
+
+ if not mono_lib_name:
+ raise RuntimeError('Could not find mono library in: ' + mono_lib_path)
+
+ if env.msvc:
+ env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX'])
+ else:
+ env.Append(LIBS=[mono_lib_name])
+
+ mono_bin_path = os.path.join(mono_root, 'bin')
+
+ mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll')
+
+ 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')
+ else:
+ is_apple = (sys.platform == 'darwin' or "osxcross" in env)
+
+ sharedlib_ext = '.dylib' if is_apple else '.so'
+
+ mono_root = mono_prefix
+ mono_lib_path = ''
+ mono_so_name = ''
+
+ 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
+ hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current', '/usr/local/var/homebrew/linked/mono']
+ for hint_dir in hint_dirs:
+ if os.path.isdir(hint_dir):
+ mono_root = hint_dir
+ break
+
+ # We can't use pkg-config to link mono statically,
+ # but we can still use it to find the mono root directory
+ if not mono_root and mono_static:
+ mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext)
+ if not mono_root:
+ raise RuntimeError("Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " + \
+ "specify one manually with the 'mono_prefix' SCons parameter")
+
+ if mono_root:
+ print('Found Mono root directory: ' + mono_root)
+
+ mono_lib_path = os.path.join(mono_root, 'lib')
+
+ env.Append(LIBPATH=mono_lib_path)
+ env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
+
+ 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(CPPDEFINES=['_REENTRANT'])
+
+ if mono_static:
+ mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a')
+
+ if is_apple:
+ env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file])
+ else:
+ env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive'])
+ else:
+ env.Append(LIBS=[mono_lib])
+
+ if is_apple:
+ env.Append(LIBS=['iconv', 'pthread'])
+ elif is_android:
+ pass # Nothing
+ else:
+ env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
+
+ if not mono_static:
+ mono_so_name = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension=sharedlib_ext)
+
+ 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)
+ else:
+ assert not mono_static
+
+ # TODO: Add option to force using pkg-config
+ print('Mono root directory not found. Using pkg-config instead')
+
+ env.ParseConfig('pkg-config monosgen-2 --libs')
+ env_mono.ParseConfig('pkg-config monosgen-2 --cflags')
+
+ tmpenv = Environment()
+ tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
+ tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
+
+ for hint_dir in tmpenv['LIBPATH']:
+ name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext)
+ if name_found:
+ mono_lib_path = hint_dir
+ mono_so_name = name_found
+ break
+
+ if not mono_so_name:
+ raise RuntimeError('Could not find mono shared library in: ' + str(tmpenv['LIBPATH']))
+
+ if not mono_static:
+ libs_output_dir = get_android_out_dir(env) if is_android else '#bin'
+ copy_file(mono_lib_path, libs_output_dir, 'lib' + mono_so_name + sharedlib_ext)
+
+ env.Append(LINKFLAGS='-rdynamic')
+
+ 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:
+ mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip()
+
+ if tools_enabled:
+ copy_mono_root_files(env, mono_root)
+ else:
+ print("Ignoring option: 'copy_mono_root'. Only available for builds with 'tools' enabled.")
+
+
+def make_template_dir(env, mono_root):
+ from shutil import rmtree
+
+ platform = env['platform']
+ target = env['target']
+
+ template_dir_name = ''
+
+ if platform in ['windows', 'osx', 'x11', 'android']:
+ template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target)
+ else:
+ assert False
+
+ output_dir = Dir('#bin').abspath
+ template_dir = os.path.join(output_dir, template_dir_name)
+
+ template_mono_root_dir = os.path.join(template_dir, 'Mono')
+
+ if os.path.isdir(template_mono_root_dir):
+ rmtree(template_mono_root_dir) # Clean first
+
+ # Copy etc/mono/
+
+ template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono')
+ copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform'])
+
+ # Copy the required shared libraries
+
+ copy_mono_shared_libs(env, mono_root, template_mono_root_dir)
+
+
+def copy_mono_root_files(env, mono_root):
+ from glob import glob
+ from shutil import copy
+ from shutil import rmtree
+
+ if not mono_root:
+ raise RuntimeError('Mono installation directory not found')
+
+ output_dir = Dir('#bin').abspath
+ editor_mono_root_dir = os.path.join(output_dir, 'GodotSharp', 'Mono')
+
+ if os.path.isdir(editor_mono_root_dir):
+ rmtree(editor_mono_root_dir) # Clean first
+
+ # Copy etc/mono/
+
+ editor_mono_config_dir = os.path.join(editor_mono_root_dir, 'etc', 'mono')
+ copy_mono_etc_dir(mono_root, editor_mono_config_dir, env['platform'])
+
+ # Copy the required shared libraries
+
+ copy_mono_shared_libs(env, mono_root, editor_mono_root_dir)
+
+ # Copy framework assemblies
+
+ mono_framework_dir = os.path.join(mono_root, 'lib', 'mono', '4.5')
+ mono_framework_facades_dir = os.path.join(mono_framework_dir, 'Facades')
+
+ editor_mono_framework_dir = os.path.join(editor_mono_root_dir, 'lib', 'mono', '4.5')
+ editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, 'Facades')
+
+ if not os.path.isdir(editor_mono_framework_dir):
+ os.makedirs(editor_mono_framework_dir)
+ if not os.path.isdir(editor_mono_framework_facades_dir):
+ os.makedirs(editor_mono_framework_facades_dir)
+
+ for assembly in glob(os.path.join(mono_framework_dir, '*.dll')):
+ copy(assembly, editor_mono_framework_dir)
+ for assembly in glob(os.path.join(mono_framework_facades_dir, '*.dll')):
+ copy(assembly, editor_mono_framework_facades_dir)
+
+
+def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform):
+ from distutils.dir_util import copy_tree
+ from glob import glob
+ from shutil import copy
+
+ if not os.path.isdir(target_mono_config_dir):
+ os.makedirs(target_mono_config_dir)
+
+ mono_etc_dir = os.path.join(mono_root, 'etc', 'mono')
+ if not os.path.isdir(mono_etc_dir):
+ mono_etc_dir = ''
+ etc_hint_dirs = []
+ if platform != 'windows':
+ etc_hint_dirs += ['/etc/mono', '/usr/local/etc/mono']
+ if 'MONO_CFG_DIR' in os.environ:
+ etc_hint_dirs += [os.path.join(os.environ['MONO_CFG_DIR'], 'mono')]
+ for etc_hint_dir in etc_hint_dirs:
+ if os.path.isdir(etc_hint_dir):
+ mono_etc_dir = etc_hint_dir
+ break
+ if not mono_etc_dir:
+ raise RuntimeError('Mono installation etc directory not found')
+
+ copy_tree(os.path.join(mono_etc_dir, '2.0'), os.path.join(target_mono_config_dir, '2.0'))
+ copy_tree(os.path.join(mono_etc_dir, '4.0'), os.path.join(target_mono_config_dir, '4.0'))
+ copy_tree(os.path.join(mono_etc_dir, '4.5'), os.path.join(target_mono_config_dir, '4.5'))
+ if os.path.isdir(os.path.join(mono_etc_dir, 'mconfig')):
+ copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig'))
+
+ for file in glob(os.path.join(mono_etc_dir, '*')):
+ if os.path.isfile(file):
+ copy(file, target_mono_config_dir)
+
+
+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'), target_mono_bin_dir)
+ else:
+ target_mono_lib_dir = get_android_out_dir(env) if platform == 'android' else os.path.join(target_mono_root_dir, 'lib')
+
+ if not os.path.isdir(target_mono_lib_dir):
+ os.makedirs(target_mono_lib_dir)
+
+ if platform == 'osx':
+ # 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'
+ ]]
+
+ 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):
+ tmpenv = Environment()
+ tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
+ tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
+ for hint_dir in tmpenv['LIBPATH']:
+ name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext)
+ if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')):
+ return os.path.join(hint_dir, '..')
+ return ''
diff --git a/modules/mono/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py
index c8ebb54ded..b2c48f0a61 100644
--- a/modules/mono/mono_reg_utils.py
+++ b/modules/mono/build_scripts/mono_reg_utils.py
@@ -40,7 +40,7 @@ def _reg_open_key_bits(key, subkey, bits):
def _find_mono_in_reg(subkey, bits):
try:
with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
- value, regtype = winreg.QueryValueEx(hKey, 'SdkInstallRoot')
+ value = winreg.QueryValueEx(hKey, 'SdkInstallRoot')[0]
return value
except (WindowsError, OSError):
return None
@@ -49,7 +49,7 @@ def _find_mono_in_reg(subkey, bits):
def _find_mono_in_reg_old(subkey, bits):
try:
with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
- default_clr, regtype = winreg.QueryValueEx(hKey, 'DefaultCLR')
+ default_clr = winreg.QueryValueEx(hKey, 'DefaultCLR')[0]
if default_clr:
return _find_mono_in_reg(subkey + '\\' + default_clr, bits)
return None
@@ -91,7 +91,13 @@ def find_msbuild_tools_path_reg():
if not val:
raise ValueError('Value of `installationPath` entry is empty')
- return os.path.join(val, "MSBuild\\15.0\\Bin")
+ # Since VS2019, the directory is simply named "Current"
+ msbuild_dir = os.path.join(val, 'MSBuild\\Current\\Bin')
+ if os.path.isdir(msbuild_dir):
+ return msbuild_dir
+
+ # Directory name "15.0" is used in VS 2017
+ return os.path.join(val, 'MSBuild\\15.0\\Bin')
raise ValueError('Cannot find `installationPath` entry')
except ValueError as e:
@@ -106,9 +112,7 @@ def find_msbuild_tools_path_reg():
try:
subkey = r'SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0'
with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey:
- value, regtype = winreg.QueryValueEx(hKey, 'MSBuildToolsPath')
+ value = winreg.QueryValueEx(hKey, 'MSBuildToolsPath')[0]
return value
except (WindowsError, OSError):
return ''
-
- return ''
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/solution_builder.py b/modules/mono/build_scripts/solution_builder.py
new file mode 100644
index 0000000000..d1529a64d2
--- /dev/null
+++ b/modules/mono/build_scripts/solution_builder.py
@@ -0,0 +1,214 @@
+
+import os
+
+
+verbose = False
+
+
+def find_nuget_unix():
+ import os
+
+ if 'NUGET_PATH' in os.environ:
+ hint_path = os.environ['NUGET_PATH']
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+ hint_path = os.path.join(hint_path, 'nuget')
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+
+ import os.path
+ import sys
+
+ hint_dirs = ['/opt/novell/mono/bin']
+ if sys.platform == 'darwin':
+ hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs
+
+ for hint_dir in hint_dirs:
+ hint_path = os.path.join(hint_dir, 'nuget')
+ if os.path.isfile(hint_path):
+ return hint_path
+ elif os.path.isfile(hint_path + '.exe'):
+ return hint_path + '.exe'
+
+ for hint_dir in os.environ['PATH'].split(os.pathsep):
+ hint_dir = hint_dir.strip('"')
+ hint_path = os.path.join(hint_dir, 'nuget')
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+ if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
+ return hint_path + '.exe'
+
+ return None
+
+
+def find_nuget_windows(env):
+ import os
+
+ if 'NUGET_PATH' in os.environ:
+ hint_path = os.environ['NUGET_PATH']
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+ hint_path = os.path.join(hint_path, 'nuget.exe')
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+
+ from . mono_reg_utils import find_mono_root_dir
+
+ mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits'])
+
+ if mono_root:
+ mono_bin_dir = os.path.join(mono_root, 'bin')
+ nuget_mono = os.path.join(mono_bin_dir, 'nuget.bat')
+
+ if os.path.isfile(nuget_mono):
+ return nuget_mono
+
+ # Standalone NuGet
+
+ for hint_dir in os.environ['PATH'].split(os.pathsep):
+ hint_dir = hint_dir.strip('"')
+ hint_path = os.path.join(hint_dir, 'nuget.exe')
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+
+ return None
+
+
+def find_msbuild_unix(filename):
+ import os.path
+ import sys
+
+ hint_dirs = ['/opt/novell/mono/bin']
+ if sys.platform == 'darwin':
+ hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs
+
+ for hint_dir in hint_dirs:
+ hint_path = os.path.join(hint_dir, filename)
+ if os.path.isfile(hint_path):
+ return hint_path
+ elif os.path.isfile(hint_path + '.exe'):
+ return hint_path + '.exe'
+
+ for hint_dir in os.environ['PATH'].split(os.pathsep):
+ hint_dir = hint_dir.strip('"')
+ hint_path = os.path.join(hint_dir, filename)
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+ if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
+ return hint_path + '.exe'
+
+ return None
+
+
+def find_msbuild_windows(env):
+ from . mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg
+
+ mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits'])
+
+ if not mono_root:
+ raise RuntimeError('Cannot find mono root directory')
+
+ mono_bin_dir = os.path.join(mono_root, 'bin')
+ msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat')
+
+ msbuild_tools_path = find_msbuild_tools_path_reg()
+
+ if msbuild_tools_path:
+ return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), {})
+
+ if os.path.isfile(msbuild_mono):
+ # The (Csc/Vbc/Fsc)ToolExe environment variables are required when
+ # building with Mono's MSBuild. They must point to the batch files
+ # in Mono's bin directory to make sure they are executed with Mono.
+ mono_msbuild_env = {
+ 'CscToolExe': os.path.join(mono_bin_dir, 'csc.bat'),
+ 'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'),
+ 'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat')
+ }
+ return (msbuild_mono, mono_msbuild_env)
+
+ return None
+
+
+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
+ 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')
+
+
+def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
+ global verbose
+ verbose = env['verbose']
+
+ msbuild_env = os.environ.copy()
+
+ # Needed when running from Developer Command Prompt for VS
+ if 'PLATFORM' in msbuild_env:
+ del msbuild_env['PLATFORM']
+
+ # Find MSBuild
+ if os.name == 'nt':
+ msbuild_info = find_msbuild_windows(env)
+ if msbuild_info is None:
+ raise RuntimeError('Cannot find MSBuild executable')
+ msbuild_path = msbuild_info[0]
+ msbuild_env.update(msbuild_info[1])
+ else:
+ msbuild_path = find_msbuild_unix('msbuild')
+ if msbuild_path is None:
+ xbuild_fallback = env['xbuild_fallback']
+
+ if xbuild_fallback and os.name == 'nt':
+ print('Option \'xbuild_fallback\' not supported on Windows')
+ xbuild_fallback = False
+
+ if xbuild_fallback:
+ print('Cannot find MSBuild executable, trying with xbuild')
+ print('Warning: xbuild is deprecated')
+
+ msbuild_path = find_msbuild_unix('xbuild')
+
+ if msbuild_path is None:
+ raise RuntimeError('Cannot find xbuild executable')
+ else:
+ raise RuntimeError('Cannot find MSBuild executable')
+
+ print('MSBuild path: ' + msbuild_path)
+
+ # Build solution
+
+ msbuild_args = [solution_path, '/p:Configuration=' + build_config]
+ msbuild_args += extra_msbuild_args
+
+ run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild')
diff --git a/modules/mono/tls_configure.py b/modules/mono/build_scripts/tls_configure.py
index 622280b00b..622280b00b 100644
--- a/modules/mono/tls_configure.py
+++ b/modules/mono/build_scripts/tls_configure.py
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
new file mode 100644
index 0000000000..bbc779601e
--- /dev/null
+++ b/modules/mono/class_db_api_json.cpp
@@ -0,0 +1,246 @@
+/*************************************************************************/
+/* class_db_api_json.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 "class_db_api_json.h"
+
+#ifdef DEBUG_METHODS_ENABLED
+
+#include "core/io/json.h"
+#include "core/os/file_access.h"
+#include "core/project_settings.h"
+#include "core/version.h"
+
+void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
+ Dictionary classes_dict;
+
+ List<StringName> names;
+
+ const StringName *k = NULL;
+
+ while ((k = ClassDB::classes.next(k))) {
+
+ names.push_back(*k);
+ }
+ //must be alphabetically sorted for hash to compute
+ names.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+
+ ClassDB::ClassInfo *t = ClassDB::classes.getptr(E->get());
+ ERR_FAIL_COND(!t);
+ if (t->api != p_api || !t->exposed)
+ continue;
+
+ Dictionary class_dict;
+ classes_dict[t->name] = class_dict;
+
+ class_dict["inherits"] = t->inherits;
+
+ { //methods
+
+ List<StringName> snames;
+
+ k = NULL;
+
+ while ((k = t->method_map.next(k))) {
+
+ snames.push_back(*k);
+ }
+
+ snames.sort_custom<StringName::AlphCompare>();
+
+ Array methods;
+
+ for (List<StringName>::Element *F = snames.front(); F; F = F->next()) {
+ Dictionary method_dict;
+ methods.push_back(method_dict);
+
+ MethodBind *mb = t->method_map[F->get()];
+ method_dict["name"] = mb->get_name();
+ method_dict["argument_count"] = mb->get_argument_count();
+ method_dict["return_type"] = mb->get_argument_type(-1);
+
+ Array arguments;
+ method_dict["arguments"] = arguments;
+
+ for (int i = 0; i < mb->get_argument_count(); i++) {
+ Dictionary argument_dict;
+ arguments.push_back(argument_dict);
+ const PropertyInfo info = mb->get_argument_info(i);
+ argument_dict["type"] = info.type;
+ argument_dict["name"] = info.name;
+ argument_dict["hint"] = info.hint;
+ argument_dict["hint_string"] = info.hint_string;
+ }
+
+ method_dict["default_argument_count"] = mb->get_default_argument_count();
+
+ Array default_arguments;
+ method_dict["default_arguments"] = default_arguments;
+
+ for (int i = 0; i < mb->get_default_argument_count(); i++) {
+ Dictionary default_argument_dict;
+ default_arguments.push_back(default_argument_dict);
+ //hash should not change, i hope for tis
+ Variant da = mb->get_default_argument(i);
+ default_argument_dict["value"] = da;
+ }
+
+ method_dict["hint_flags"] = mb->get_hint_flags();
+ }
+
+ if (!methods.empty()) {
+ class_dict["methods"] = methods;
+ }
+ }
+
+ { //constants
+
+ List<StringName> snames;
+
+ k = NULL;
+
+ while ((k = t->constant_map.next(k))) {
+
+ snames.push_back(*k);
+ }
+
+ snames.sort_custom<StringName::AlphCompare>();
+
+ Array constants;
+
+ for (List<StringName>::Element *F = snames.front(); F; F = F->next()) {
+ Dictionary constant_dict;
+ constants.push_back(constant_dict);
+
+ constant_dict["name"] = F->get();
+ constant_dict["value"] = t->constant_map[F->get()];
+ }
+
+ if (!constants.empty()) {
+ class_dict["constants"] = constants;
+ }
+ }
+
+ { //signals
+
+ List<StringName> snames;
+
+ k = NULL;
+
+ while ((k = t->signal_map.next(k))) {
+
+ snames.push_back(*k);
+ }
+
+ snames.sort_custom<StringName::AlphCompare>();
+
+ Array signals;
+
+ for (List<StringName>::Element *F = snames.front(); F; F = F->next()) {
+ Dictionary signal_dict;
+ signals.push_back(signal_dict);
+
+ MethodInfo &mi = t->signal_map[F->get()];
+ signal_dict["name"] = F->get();
+
+ Array arguments;
+ signal_dict["arguments"] = arguments;
+ for (int i = 0; i < mi.arguments.size(); i++) {
+ Dictionary argument_dict;
+ arguments.push_back(argument_dict);
+ argument_dict["type"] = mi.arguments[i].type;
+ }
+ }
+
+ if (!signals.empty()) {
+ class_dict["signals"] = signals;
+ }
+ }
+
+ { //properties
+
+ List<StringName> snames;
+
+ k = NULL;
+
+ while ((k = t->property_setget.next(k))) {
+
+ snames.push_back(*k);
+ }
+
+ snames.sort_custom<StringName::AlphCompare>();
+
+ Array properties;
+
+ for (List<StringName>::Element *F = snames.front(); F; F = F->next()) {
+ Dictionary property_dict;
+ properties.push_back(property_dict);
+
+ ClassDB::PropertySetGet *psg = t->property_setget.getptr(F->get());
+
+ property_dict["name"] = F->get();
+ property_dict["setter"] = psg->setter;
+ property_dict["getter"] = psg->getter;
+ }
+
+ if (!properties.empty()) {
+ class_dict["property_setget"] = properties;
+ }
+ }
+
+ Array property_list;
+
+ //property list
+ for (List<PropertyInfo>::Element *F = t->property_list.front(); F; F = F->next()) {
+ Dictionary property_dict;
+ property_list.push_back(property_dict);
+
+ property_dict["name"] = F->get().name;
+ property_dict["type"] = F->get().type;
+ property_dict["hint"] = F->get().hint;
+ property_dict["hint_string"] = F->get().hint_string;
+ property_dict["usage"] = F->get().usage;
+ }
+
+ if (!property_list.empty()) {
+ class_dict["property_list"] = property_list;
+ }
+ }
+
+ FileAccessRef f = FileAccess::open(p_output_file, FileAccess::WRITE);
+ ERR_FAIL_COND_MSG(!f, "Cannot open file '" + p_output_file + "'.");
+ f->store_string(JSON::print(classes_dict, /*indent: */ "\t"));
+ f->close();
+
+ print_line(String() + "ClassDB API JSON written to: " + ProjectSettings::get_singleton()->globalize_path(p_output_file));
+}
+
+#endif // DEBUG_METHODS_ENABLED
diff --git a/modules/mono/editor/net_solution.h b/modules/mono/class_db_api_json.h
index bdff24af0b..9888ecfb55 100644
--- a/modules/mono/editor/net_solution.h
+++ b/modules/mono/class_db_api_json.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* net_solution.h */
+/* class_db_api_json.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -28,31 +28,19 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef NET_SOLUTION_H
-#define NET_SOLUTION_H
+#ifndef CLASS_DB_API_JSON_H
+#define CLASS_DB_API_JSON_H
-#include "core/map.h"
-#include "core/ustring.h"
-
-struct NETSolution {
- String name;
-
- void add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs = Vector<String>());
+// 'core/method_bind.h' defines DEBUG_METHODS_ENABLED, but it looks like we
+// cannot include it here. That's why we include it through 'core/class_db.h'.
+#include "core/class_db.h"
- Error save();
+#ifdef DEBUG_METHODS_ENABLED
- bool set_path(const String &p_existing_path);
-
- NETSolution(const String &p_name);
+#include "core/ustring.h"
-private:
- struct ProjectInfo {
- String guid;
- Vector<String> configs;
- };
+void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api);
- String path;
- Map<String, ProjectInfo> projects;
-};
+#endif // DEBUG_METHODS_ENABLED
-#endif // NET_SOLUTION_H
+#endif // CLASS_DB_API_JSON_H
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 01649a972e..9adf4ee6e5 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -1,25 +1,22 @@
-
-import imp
-import os
-import sys
-import subprocess
-
-from distutils.version import LooseVersion
-from SCons.Script import BoolVariable, Dir, Environment, File, PathVariable, SCons, Variables
-
-
-monoreg = imp.load_source('mono_reg_utils', 'modules/mono/mono_reg_utils.py')
-
-
def can_build(env, platform):
if platform in ['javascript']:
return False # Not yet supported
return True
-def is_enabled():
- # The module is disabled by default. Use module_mono_enabled=yes to enable it.
- return False
+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():
@@ -34,279 +31,6 @@ def get_doc_path():
return 'doc_classes'
-def find_file_in_dir(directory, files, prefix='', extension=''):
- if not extension.startswith('.'):
- extension = '.' + extension
- for curfile in files:
- if os.path.isfile(os.path.join(directory, prefix + curfile + extension)):
- return curfile
- return ''
-
-
-def copy_file(src_dir, dst_dir, name):
- from shutil import copyfile
-
- src_path = os.path.join(src_dir, name)
- dst_path = os.path.join(dst_dir, name)
-
- if not os.path.isdir(dst_dir):
- os.mkdir(dst_dir)
-
- copyfile(src_path, dst_path)
-
-
-def custom_path_is_dir_create(key, val, env):
- """Validator to check if Path is a directory, creating it if it does not exist.
- Similar to PathIsDirCreate, except it uses SCons.Script.Dir() and
- SCons.Script.File() in order to support the '#' top level directory token.
- """
- # Dir constructor will throw an error if the path points to a file
- fsDir = Dir(val)
- if not fsDir.exists:
- os.makedirs(fsDir.abspath)
-
-
-def configure(env):
- env.use_ptrcall = True
- env.add_module_version_string('mono')
-
- envvars = Variables()
- envvars.Add(BoolVariable('mono_static', 'Statically link mono', False))
- envvars.Add(PathVariable('mono_assemblies_output_dir', 'Path to the assemblies output directory', '#bin', custom_path_is_dir_create))
- envvars.Update(env)
-
- bits = env['bits']
-
- mono_static = env['mono_static']
- assemblies_output_dir = Dir(env['mono_assemblies_output_dir']).abspath
-
- mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0']
-
- if env['platform'] == 'windows':
- mono_root = ''
-
- 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:
- raise RuntimeError('Mono installation directory not found')
-
- print('Found Mono root directory: ' + mono_root)
-
- mono_version = mono_root_try_find_mono_version(mono_root)
- configure_for_mono_version(env, mono_version)
-
- mono_lib_path = os.path.join(mono_root, 'lib')
-
- env.Append(LIBPATH=mono_lib_path)
- env.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
-
- if mono_static:
- lib_suffix = Environment()['LIBSUFFIX']
-
- if env.msvc:
- mono_static_lib_name = 'libmono-static-sgen'
- else:
- mono_static_lib_name = 'libmonosgen-2.0'
-
- if not os.path.isfile(os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)):
- raise RuntimeError('Could not find static mono library in: ' + mono_lib_path)
-
- if env.msvc:
- env.Append(LINKFLAGS=mono_static_lib_name + lib_suffix)
-
- env.Append(LINKFLAGS='Mincore' + lib_suffix)
- env.Append(LINKFLAGS='msvcrt' + lib_suffix)
- env.Append(LINKFLAGS='LIBCMT' + lib_suffix)
- env.Append(LINKFLAGS='Psapi' + lib_suffix)
- else:
- env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix))
-
- env.Append(LIBS='psapi')
- env.Append(LIBS='version')
- else:
- mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib')
-
- if not mono_lib_name:
- raise RuntimeError('Could not find mono library in: ' + mono_lib_path)
-
- if env.msvc:
- env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX'])
- else:
- env.Append(LIBS=mono_lib_name)
-
- mono_bin_path = os.path.join(mono_root, 'bin')
-
- mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll')
-
- 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(os.path.join(mono_lib_path, 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll')
- else:
- sharedlib_ext = '.dylib' if sys.platform == 'darwin' else '.so'
-
- mono_root = ''
- mono_lib_path = ''
-
- 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 sys.platform == 'darwin':
- # Try with some known directories under OSX
- hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current', '/usr/local/var/homebrew/linked/mono']
- for hint_dir in hint_dirs:
- if os.path.isdir(hint_dir):
- mono_root = hint_dir
- break
-
- # We can't use pkg-config to link mono statically,
- # but we can still use it to find the mono root directory
- 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')
-
- 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_version)
-
- mono_lib_path = os.path.join(mono_root, 'lib')
-
- env.Append(LIBPATH=mono_lib_path)
- env.Append(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.Append(CPPFLAGS=['-D_REENTRANT'])
-
- if mono_static:
- mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a')
-
- if sys.platform == 'darwin':
- env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file])
- elif sys.platform == 'linux' or sys.platform == 'linux2':
- env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive'])
- else:
- raise RuntimeError('mono-static: Not supported on this platform')
- else:
- env.Append(LIBS=[mono_lib])
-
- if sys.platform == 'darwin':
- env.Append(LIBS=['iconv', 'pthread'])
- elif sys.platform == 'linux' or sys.platform == 'linux2':
- env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
-
- if not mono_static:
- mono_so_name = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension=sharedlib_ext)
-
- 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(os.path.join(mono_lib_path, 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll')
- 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_version)
-
- env.ParseConfig('pkg-config monosgen-2 --cflags --libs')
-
- mono_lib_path = ''
- mono_so_name = ''
- mono_prefix = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip()
-
- tmpenv = Environment()
- tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
- tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
-
- for hint_dir in tmpenv['LIBPATH']:
- name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext)
- if name_found:
- mono_lib_path = hint_dir
- mono_so_name = name_found
- break
-
- 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)
- copy_file(os.path.join(mono_prefix, 'lib', 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll')
-
- env.Append(LINKFLAGS='-rdynamic')
-
-
-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'])
-
-
-def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
- tmpenv = Environment()
- tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
- tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
- for hint_dir in tmpenv['LIBPATH']:
- name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext)
- if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')):
- return os.path.join(hint_dir, '..')
- return ''
-
-
-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
+def is_enabled():
+ # The module is disabled by default. Use module_mono_enabled=yes to enable it.
+ return False
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index c013c232d4..83be10dee3 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -32,6 +32,7 @@
#include <mono/metadata/threads.h>
+#include "core/io/json.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/os/thread.h"
@@ -41,15 +42,20 @@
#include "editor/bindings_generator.h"
#include "editor/csharp_project.h"
#include "editor/editor_node.h"
-#include "editor/godotsharp_editor.h"
-#include "utils/string_utils.h"
#endif
+#ifdef DEBUG_METHODS_ENABLED
+#include "class_db_api_json.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"
#include "signal_awaiter_utils.h"
#include "utils/macros.h"
+#include "utils/mutex_utils.h"
+#include "utils/string_utils.h"
#include "utils/thread_local.h"
#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var)
@@ -63,8 +69,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;
@@ -94,32 +100,36 @@ Error CSharpLanguage::execute_file(const String &p_path) {
return OK;
}
-#ifdef TOOLS_ENABLED
-void gdsharp_editor_init_callback() {
+void CSharpLanguage::init() {
- EditorNode *editor = EditorNode::get_singleton();
- editor->add_child(memnew(GodotSharpEditor(editor)));
-}
+#ifdef DEBUG_METHODS_ENABLED
+ if (OS::get_singleton()->get_cmdline_args().find("--class_db_to_json")) {
+ class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE);
+#ifdef TOOLS_ENABLED
+ class_db_api_to_json("user://class_db_api_editor.json", ClassDB::API_EDITOR);
+#endif
+ }
#endif
-
-void CSharpLanguage::init() {
gdmono = memnew(GDMono);
gdmono->initialize();
-#ifndef MONO_GLUE_ENABLED
- WARN_PRINT("This binary is built with `mono_glue=no` and cannot be used for scripting");
+#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
+ // Generate bindings here, before loading assemblies. 'initialize_load_assemblies' aborts
+ // the applications if the api assemblies or the main tools assembly is missing, but this
+ // is not a problem for BindingsGenerator as it only needs the tools project editor assembly.
+ List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
+ BindingsGenerator::handle_cmdline_args(cmdline_args);
#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);
- }
+#ifndef MONO_GLUE_ENABLED
+ print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'");
#endif
+ gdmono->initialize_load_assemblies();
+
#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
@@ -129,22 +139,24 @@ 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();
- // Release gchandle bindings before finalizing mono runtime
- script_bindings.clear();
+ if (script_binding.gchandle.is_valid()) {
+ script_binding.gchandle->release();
+ script_binding.inited = false;
+ }
+ }
if (gdmono) {
memdelete(gdmono);
gdmono = NULL;
}
+ // Clear here, after finalizing all domains to make sure there is nothing else referencing the elements.
+ script_bindings.clear();
+
finalizing = false;
}
@@ -370,70 +382,82 @@ bool CSharpLanguage::supports_builtin_mode() const {
return false;
}
+#ifdef TOOLS_ENABLED
static String variant_type_to_managed_name(const String &p_var_type_name) {
if (p_var_type_name.empty())
return "object";
if (!ClassDB::class_exists(p_var_type_name)) {
- Variant::Type var_types[] = {
- Variant::BOOL,
- Variant::INT,
- Variant::REAL,
- Variant::STRING,
- Variant::VECTOR2,
- Variant::RECT2,
- Variant::VECTOR3,
- Variant::TRANSFORM2D,
- Variant::PLANE,
- Variant::QUAT,
- Variant::AABB,
- Variant::BASIS,
- Variant::TRANSFORM,
- Variant::COLOR,
- Variant::NODE_PATH,
- Variant::_RID
- };
-
- for (int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) {
- if (p_var_type_name == Variant::get_type_name(var_types[i]))
- return p_var_type_name;
- }
+ return p_var_type_name;
+ }
- if (p_var_type_name == "String")
- return "string"; // I prefer this one >:[
+ if (p_var_type_name == Variant::get_type_name(Variant::OBJECT))
+ return "Godot.Object";
- // TODO these will be rewritten later into custom containers
+ if (p_var_type_name == Variant::get_type_name(Variant::REAL)) {
+#ifdef REAL_T_IS_DOUBLE
+ return "double";
+#else
+ return "float";
+#endif
+ }
- if (p_var_type_name == "Array")
- return "object[]";
+ if (p_var_type_name == Variant::get_type_name(Variant::STRING))
+ return "string"; // I prefer this one >:[
- if (p_var_type_name == "Dictionary")
- return "Dictionary<object, object>";
+ if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY))
+ return "Collections.Dictionary";
- if (p_var_type_name == "PoolByteArray")
- return "byte[]";
- if (p_var_type_name == "PoolIntArray")
- return "int[]";
- if (p_var_type_name == "PoolRealArray")
- return "float[]";
- if (p_var_type_name == "PoolStringArray")
- return "string[]";
- if (p_var_type_name == "PoolVector2Array")
- return "Vector2[]";
- if (p_var_type_name == "PoolVector3Array")
- return "Vector3[]";
- if (p_var_type_name == "PoolColorArray")
- return "Color[]";
+ if (p_var_type_name == Variant::get_type_name(Variant::ARRAY))
+ return "Collections.Array";
- return "object";
+ if (p_var_type_name == Variant::get_type_name(Variant::POOL_BYTE_ARRAY))
+ return "byte[]";
+ if (p_var_type_name == Variant::get_type_name(Variant::POOL_INT_ARRAY))
+ return "int[]";
+ if (p_var_type_name == Variant::get_type_name(Variant::POOL_REAL_ARRAY)) {
+#ifdef REAL_T_IS_DOUBLE
+ return "double[]";
+#else
+ return "float[]";
+#endif
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::POOL_STRING_ARRAY))
+ return "string[]";
+ if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR2_ARRAY))
+ return "Vector2[]";
+ if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR3_ARRAY))
+ return "Vector3[]";
+ if (p_var_type_name == Variant::get_type_name(Variant::POOL_COLOR_ARRAY))
+ return "Color[]";
+
+ Variant::Type var_types[] = {
+ Variant::BOOL,
+ Variant::INT,
+ Variant::VECTOR2,
+ Variant::RECT2,
+ Variant::VECTOR3,
+ Variant::TRANSFORM2D,
+ Variant::PLANE,
+ Variant::QUAT,
+ Variant::AABB,
+ Variant::BASIS,
+ Variant::TRANSFORM,
+ Variant::COLOR,
+ Variant::NODE_PATH,
+ Variant::_RID
+ };
+
+ for (unsigned int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) {
+ if (p_var_type_name == Variant::get_type_name(var_types[i]))
+ return p_var_type_name;
}
- return p_var_type_name;
+ return "object";
}
-String CSharpLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const {
-#ifdef TOOLS_ENABLED
+String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const {
// FIXME
// - Due to Godot's API limitation this just appends the function to the end of the file
// - Use fully qualified name if there is ambiguity
@@ -449,10 +473,12 @@ String CSharpLanguage::make_function(const String &p_class, const String &p_name
s += ")\n{\n // Replace with function body.\n}\n";
return s;
+}
#else
+String CSharpLanguage::make_function(const String &, const String &, const PoolStringArray &) const {
return String();
-#endif
}
+#endif
String CSharpLanguage::_get_indentation() const {
#ifdef TOOLS_ENABLED
@@ -473,6 +499,47 @@ String CSharpLanguage::_get_indentation() const {
return "\t";
}
+String CSharpLanguage::debug_get_error() const {
+
+ return _debug_error;
+}
+
+int CSharpLanguage::debug_get_stack_level_count() const {
+
+ if (_debug_parse_err_line >= 0)
+ return 1;
+
+ // TODO: StackTrace
+ return 1;
+}
+
+int CSharpLanguage::debug_get_stack_level_line(int p_level) const {
+
+ if (_debug_parse_err_line >= 0)
+ return _debug_parse_err_line;
+
+ // TODO: StackTrace
+ return 1;
+}
+
+String CSharpLanguage::debug_get_stack_level_function(int p_level) const {
+
+ if (_debug_parse_err_line >= 0)
+ return String();
+
+ // TODO: StackTrace
+ return String();
+}
+
+String CSharpLanguage::debug_get_stack_level_source(int p_level) const {
+
+ if (_debug_parse_err_line >= 0)
+ return _debug_parse_err_file;
+
+ // TODO: StackTrace
+ return String();
+}
+
Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() {
#ifdef DEBUG_ENABLED
@@ -504,8 +571,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
MonoException *exc = NULL;
- GDMonoUtils::StackTrace_GetFrames st_get_frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames);
- MonoArray *frames = st_get_frames(p_stack_trace, (MonoObject **)&exc);
+ MonoArray *frames = invoke_method_thunk(CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames), p_stack_trace, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
@@ -529,7 +595,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
MonoString *file_name;
int file_line_num;
MonoString *method_decl;
- get_sf_info(frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc);
+ invoke_method_thunk(get_sf_info, frame, &file_name, &file_line_num, &method_decl, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
@@ -558,14 +624,11 @@ void CSharpLanguage::frame() {
MonoObject *task_scheduler = task_scheduler_handle->get_target();
if (task_scheduler) {
- GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate);
-
MonoException *exc = NULL;
- thunk(task_scheduler, (MonoObject **)&exc);
+ invoke_method_thunk(CACHED_METHOD_THUNK(GodotTaskScheduler, Activate), task_scheduler, &exc);
if (exc) {
GDMonoUtils::debug_unhandled_exception(exc);
- _UNREACHABLE_();
}
}
}
@@ -594,33 +657,9 @@ struct CSharpScriptDepSort {
void CSharpLanguage::reload_all_scripts() {
-#ifdef DEBUG_ENABLED
-
-#ifndef NO_THREADS
- lock->lock();
-#endif
-
- List<Ref<CSharpScript> > scripts;
-
- SelfList<CSharpScript> *elem = script_list.first();
- while (elem) {
- if (elem->self()->get_path().is_resource_file()) {
- scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident
- }
- elem = elem->next();
- }
-
-#ifndef NO_THREADS
- lock->unlock();
-#endif
-
- //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()) {
- E->get()->load_source_code(E->get()->get_path());
- E->get()->reload(true);
+#ifdef GD_MONO_HOT_RELOAD
+ if (is_assembly_reloading_needed()) {
+ reload_assemblies(false);
}
#endif
}
@@ -629,199 +668,397 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
(void)p_script; // UNUSED
+ CRASH_COND(!Engine::get_singleton()->is_editor_hint());
+
#ifdef TOOLS_ENABLED
- MonoReloadNode::get_singleton()->restart_reload_timer();
- reload_assemblies_if_needed(p_soft_reload);
+ get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
+#endif
+
+#ifdef GD_MONO_HOT_RELOAD
+ if (is_assembly_reloading_needed()) {
+ reload_assemblies(p_soft_reload);
+ }
#endif
}
-#ifdef TOOLS_ENABLED
-void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) {
+#ifdef GD_MONO_HOT_RELOAD
+bool CSharpLanguage::is_assembly_reloading_needed() {
if (!gdmono->is_runtime_initialized())
- return;
+ return false;
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; // No assembly to load
+ return false; // No assembly to load
}
if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
- return; // Already up to date
+ return false; // Already up to date
} else {
- if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name)))
- return; // No assembly to load
+ if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe)))
+ return false; // No assembly to load
}
- if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE))
- return; // The core API assembly to load is invalidated
+ return true;
+}
- if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR))
- return; // The editor API assembly to load is invalidated
+void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
-#ifndef NO_THREADS
- lock->lock();
-#endif
+ if (!gdmono->is_runtime_initialized())
+ return;
+
+ // There is no soft reloading with Mono. It's always hard reloading.
List<Ref<CSharpScript> > scripts;
- SelfList<CSharpScript> *elem = script_list.first();
- while (elem) {
- if (elem->self()->get_path().is_resource_file()) {
+ {
+ SCOPED_MUTEX_LOCK(script_instances_mutex);
- scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to CSharpScript to avoid being erased by accident
+ for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
+ // Cast to CSharpScript to avoid being erased by accident
+ scripts.push_back(Ref<CSharpScript>(elem->self()));
}
- elem = elem->next();
}
-#ifndef NO_THREADS
- lock->unlock();
-#endif
+ List<Ref<CSharpScript> > to_reload;
- //when someone asks you why dynamically typed languages are easier to write....
+ // We need to keep reference instances alive during reloading
+ List<Ref<Reference> > ref_instances;
- Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload;
+ 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
+ // As scripts are going to be reloaded, must proceed without locking here
- scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order
+ 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.insert(E->get(), Map<ObjectID, List<Pair<StringName, Variant> > >());
+ to_reload.push_back(script);
- if (!p_soft_reload) {
+ 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();
+ }
- //save state and remove script from instances
- Map<ObjectID, List<Pair<StringName, Variant> > > &map = to_reload[E->get()];
+ // 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.
- while (E->get()->instances.front()) {
- Object *obj = E->get()->instances.front()->get();
- //save instance info
- List<Pair<StringName, Variant> > state;
- if (obj->get_script_instance()) {
+ for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
+ Object *obj = F->get();
+ script->pending_reload_instances.insert(obj->get_instance_id());
- obj->get_script_instance()->get_property_state(state);
+ Reference *ref = Object::cast_to<Reference>(obj);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
+ }
+ }
- Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
- if (gchandle.is_valid())
- gchandle->release();
+#ifdef TOOLS_ENABLED
+ for (Set<PlaceHolderScriptInstance *>::Element *F = script->placeholders.front(); F; F = F->next()) {
+ Object *obj = F->get()->get_owner();
+ script->pending_reload_instances.insert(obj->get_instance_id());
- map[obj->get_instance_id()] = state;
- obj->set_script(RefPtr());
- }
+ Reference *ref = Object::cast_to<Reference>(obj);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
}
+ }
+#endif
- //same thing for placeholders
- while (E->get()->placeholders.size()) {
-
- Object *obj = E->get()->placeholders.front()->get()->get_owner();
- //save instance info
- List<Pair<StringName, Variant> > state;
- if (obj->get_script_instance()) {
- obj->get_script_instance()->get_property_state(state);
- map[obj->get_instance_id()] = state;
- obj->set_script(RefPtr());
- } else {
- // no instance found. Let's remove it so we don't loop forever
- E->get()->placeholders.erase(E->get()->placeholders.front()->get());
- }
- }
+ // Save state and remove script from instances
+ Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state;
- for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get()->pending_reload_state.front(); F; F = F->next()) {
- map[F->key()] = F->get(); //pending to reload, use this one instead
- }
+ for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
+ Object *obj = F->get();
+
+ ERR_CONTINUE(!obj->get_script_instance());
+
+ CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
+
+ // Call OnBeforeSerialize
+ if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
+ obj->get_script_instance()->call_multilevel(string_names.on_before_serialize);
+
+ // Save instance info
+ CSharpScript::StateBackup state;
- E->get()->_clear();
+ // 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)
+ }
+
+ script->_clear();
+ }
+
+ // Do domain reload
if (gdmono->reload_scripts_domain() != OK) {
// Failed to reload the scripts domain
// Make sure to add the scripts back to their owners before returning
- for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) {
- Ref<CSharpScript> scr = E->key();
- for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) {
+ for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
+ Ref<CSharpScript> scr = E->get();
+
+ for (const Map<ObjectID, CSharpScript::StateBackup>::Element *F = scr->pending_reload_state.front(); F; F = F->next()) {
Object *obj = ObjectDB::get_instance(F->key());
+
if (!obj)
continue;
+
+ ObjectID obj_id = obj->get_instance_id();
+
+ // Use a placeholder for now to avoid losing the state when saving a scene
+
obj->set_script(scr.get_ref_ptr());
- // Save reload state for next time if not saved
- if (!scr->pending_reload_state.has(obj->get_instance_id())) {
- scr->pending_reload_state[obj->get_instance_id()] = F->get();
+
+ PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj);
+ obj->set_script_instance(placeholder);
+
+#ifdef TOOLS_ENABLED
+ // Even though build didn't fail, this tells the placeholder to keep properties and
+ // it allows using property_set_fallback for restoring the state without a valid script.
+ scr->placeholder_fallback_enabled = true;
+#endif
+
+ // Restore Variant properties state, it will be kept by the placeholder until the next script reloading
+ for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
+ placeholder->property_set_fallback(G->get().first, G->get().second, NULL);
}
+
+ scr->pending_reload_state.erase(obj_id);
}
}
+
return;
}
- for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) {
+ 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->key();
- scr->exports_invalidated = true;
- scr->signals_invalidated = true;
- scr->reload(p_soft_reload);
- scr->update_exports();
+ if (!script->get_path().empty()) {
+#ifdef TOOLS_ENABLED
+ script->exports_invalidated = true;
+#endif
+ script->signals_invalidated = true;
- //restore state if saved
- for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) {
+ script->reload(p_soft_reload);
+ script->update_exports();
- Object *obj = ObjectDB::get_instance(F->key());
- if (!obj)
+ 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);
- if (!p_soft_reload) {
- //clear it just in case (may be a pending reload state)
- obj->set_script(RefPtr());
+#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);
}
- obj->set_script(scr.get_ref_ptr());
- if (!obj->get_script_instance()) {
- //failed, save reload state for next time if not saved
- if (!scr->pending_reload_state.has(obj->get_instance_id())) {
- scr->pending_reload_state[obj->get_instance_id()] = F->get();
- }
+#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;
}
- if (scr->valid && scr->is_tool() && obj->get_script_instance()->is_placeholder()) {
- // Script instance was a placeholder, but now the script was built successfully and is a tool script.
- // We have to replace the placeholder with an actual C# script instance.
- scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(obj->get_script_instance()));
- ScriptInstance *script_instance = scr->instance_create(obj);
- obj->set_script_instance(script_instance); // Not necessary as it's already done in instance_create, but just in case...
+ 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;
}
- for (List<Pair<StringName, Variant> >::Element *G = F->get().front(); G; G = G->next()) {
+ 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 = script->pending_reload_instances.front(); F; F = F->next()) {
+ ObjectID obj_id = F->get();
+ Object *obj = ObjectDB::get_instance(obj_id);
+
+ if (!obj) {
+ 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;
+ }
+
+ ScriptInstance *si = obj->get_script_instance();
+
+#ifdef TOOLS_ENABLED
+ if (si) {
+ // If the script instance is not null, then it must be a placeholder.
+ // Non-placeholder script instances are removed in godot_icall_Object_Disposed.
+ CRASH_COND(!si->is_placeholder());
+
+ if (script->is_tool() || ScriptServer::is_scripting_enabled()) {
+ // Replace placeholder with a script instance
+
+ 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 = script->instance_create(obj);
+
+ if (script_instance) {
+ script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
+ obj->set_script_instance(script_instance);
+ }
+ }
+
+ continue;
+ }
+#else
+ CRASH_COND(si != NULL);
+#endif
+ // Re-create script instance
+ obj->set_script(script.get_ref_ptr()); // will create the script instance as well
+ }
+ }
+
+ to_reload_state.push_back(script);
+ }
+
+ for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) {
+ Ref<CSharpScript> script = E->get();
+
+ 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) {
+ script->pending_reload_state.erase(obj_id);
+ continue;
+ }
+
+ 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);
}
- scr->pending_reload_state.erase(obj->get_instance_id()); //as it reloaded, remove pending state
+ // Call OnAfterDeserialization
+ CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
+ if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
+ obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
}
- //if instance states were saved, set them!
+ script->pending_reload_instances.clear();
}
+#ifdef TOOLS_ENABLED
+ // FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative.
if (Engine::get_singleton()->is_editor_hint()) {
EditorNode::get_singleton()->get_inspector()->update_tree();
NodeDock::singleton->update_lists();
}
+#endif
}
#endif
+void CSharpLanguage::_load_scripts_metadata() {
+
+ scripts_metadata.clear();
+
+ String scripts_metadata_filename = "scripts_metadata.";
+
+#ifdef TOOLS_ENABLED
+ scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player";
+#else
+#ifdef DEBUG_ENABLED
+ scripts_metadata_filename += "debug";
+#else
+ scripts_metadata_filename += "release";
+#endif
+#endif
+
+ String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename);
+
+ if (FileAccess::exists(scripts_metadata_path)) {
+ String old_json;
+
+ Error ferr = read_all_file_utf8(scripts_metadata_path, old_json);
+
+ ERR_FAIL_COND(ferr != OK);
+
+ Variant old_dict_var;
+ String err_str;
+ int err_line;
+ Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line);
+ if (json_err != OK) {
+ ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ").");
+ return;
+ }
+
+ scripts_metadata = old_dict_var.operator Dictionary();
+ scripts_metadata_invalidated = false;
+
+ print_verbose("Successfully loaded scripts metadata");
+ } else {
+ if (!Engine::get_singleton()->is_editor_hint()) {
+ ERR_PRINT("Missing scripts metadata file.");
+ }
+ }
+}
+
void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("cs");
@@ -830,12 +1067,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
@@ -859,13 +1096,12 @@ void CSharpLanguage::thread_exit() {
bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
- // Break because of parse error
+ // Not a parser error in our case, but it's still used for other type of errors
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
- // TODO
- //_debug_parse_err_line = p_line;
- //_debug_parse_err_file = p_file;
- //_debug_error = p_error;
- ScriptDebugger::get_singleton()->debug(this, false);
+ _debug_parse_err_line = p_line;
+ _debug_parse_err_file = p_file;
+ _debug_error = p_error;
+ ScriptDebugger::get_singleton()->debug(this, false, true);
return true;
} else {
return false;
@@ -875,10 +1111,9 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S
bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
- // TODO
- //_debug_parse_err_line = -1;
- //_debug_parse_err_file = "";
- //_debug_error = p_error;
+ _debug_parse_err_line = -1;
+ _debug_parse_err_file = "";
+ _debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, p_allow_continue);
return true;
} else {
@@ -886,6 +1121,43 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
}
}
+void CSharpLanguage::_on_scripts_domain_unloaded() {
+ for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
+ CSharpScriptBinding &script_binding = E->value();
+ script_binding.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) {
ERR_FAIL_COND(lang_idx != -1);
@@ -894,27 +1166,18 @@ void CSharpLanguage::set_language_index(int p_idx) {
void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) {
- if (!p_gchandle->is_released()) { // Do not locking unnecessarily
-#ifndef NO_THREADS
- get_singleton()->script_gchandle_release_lock->lock();
-#endif
-
+ if (!p_gchandle->is_released()) { // Do not lock unnecessarily
+ SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
p_gchandle->release();
-
-#ifndef NO_THREADS
- get_singleton()->script_gchandle_release_lock->unlock();
-#endif
}
}
-void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle) {
+void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle) {
- uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_pinned_expected_obj); // we might lock after this, so pin it
+ uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it
- if (!p_gchandle->is_released()) { // Do not locking unnecessarily
-#ifndef NO_THREADS
- get_singleton()->script_gchandle_release_lock->lock();
-#endif
+ if (!p_gchandle->is_released()) { // Do not lock unnecessarily
+ SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
MonoObject *target = p_gchandle->get_target();
@@ -922,13 +1185,9 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj,
// already released and could have been replaced) or if we can't get its target MonoObject*
// (which doesn't necessarily mean it was released, and we want it released in order to
// avoid locking other threads unnecessarily).
- if (target == p_pinned_expected_obj || target == NULL) {
+ if (target == p_expected_obj || target == NULL) {
p_gchandle->release();
}
-
-#ifndef NO_THREADS
- get_singleton()->script_gchandle_release_lock->unlock();
-#endif
}
MonoGCHandle::free_handle(pinned_gchandle);
@@ -936,7 +1195,7 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj,
CSharpLanguage::CSharpLanguage() {
- ERR_FAIL_COND(singleton);
+ ERR_FAIL_COND_MSG(singleton, "C# singleton already exist.");
singleton = this;
finalizing = false;
@@ -944,46 +1203,54 @@ CSharpLanguage::CSharpLanguage() {
gdmono = NULL;
#ifdef NO_THREADS
- lock = NULL;
- gchandle_bind_lock = NULL;
- script_gchandle_release_lock = NULL;
+ script_instances_mutex = NULL;
+ script_gchandle_release_mutex = NULL;
+ language_bind_mutex = NULL;
#else
- lock = Mutex::create();
- script_bind_lock = Mutex::create();
- script_gchandle_release_lock = Mutex::create();
+ script_instances_mutex = Mutex::create();
+ script_gchandle_release_mutex = Mutex::create();
+ language_bind_mutex = Mutex::create();
#endif
lang_idx = -1;
+
+ scripts_metadata_invalidated = true;
+
+#ifdef TOOLS_ENABLED
+ godotsharp_editor = NULL;
+#endif
}
CSharpLanguage::~CSharpLanguage() {
finish();
- if (lock) {
- memdelete(lock);
- lock = NULL;
+ if (script_instances_mutex) {
+ memdelete(script_instances_mutex);
+ script_instances_mutex = NULL;
}
- if (script_bind_lock) {
- memdelete(script_bind_lock);
- script_bind_lock = NULL;
+ if (language_bind_mutex) {
+ memdelete(language_bind_mutex);
+ language_bind_mutex = NULL;
}
- if (script_gchandle_release_lock) {
- memdelete(script_gchandle_release_lock);
- script_gchandle_release_lock = NULL;
+ if (script_gchandle_release_mutex) {
+ memdelete(script_gchandle_release_mutex);
+ script_gchandle_release_mutex = NULL;
}
singleton = NULL;
}
-void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
+bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object) {
#ifdef DEBUG_ENABLED
// I don't trust you
- if (p_object->get_script_instance())
- CRASH_COND(NULL != CAST_CSHARP_INSTANCE(p_object->get_script_instance()));
+ if (p_object->get_script_instance()) {
+ CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance());
+ CRASH_COND(csharp_instance != NULL && !csharp_instance->is_destructing_script_instance());
+ }
#endif
StringName type_name = p_object->get_class_name();
@@ -992,32 +1259,22 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
const ClassDB::ClassInfo *classinfo = ClassDB::classes.getptr(type_name);
while (classinfo && !classinfo->exposed)
classinfo = classinfo->inherits_ptr;
- ERR_FAIL_NULL_V(classinfo, NULL);
+ ERR_FAIL_NULL_V(classinfo, false);
type_name = classinfo->name;
GDMonoClass *type_class = GDMonoUtils::type_get_proxy_class(type_name);
- ERR_FAIL_NULL_V(type_class, NULL);
+ ERR_FAIL_NULL_V(type_class, false);
MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(type_class, type_name, p_object);
- ERR_FAIL_NULL_V(mono_object, NULL);
-
- CSharpScriptBinding script_binding;
-
- script_binding.type_name = type_name;
- script_binding.wrapper_class = type_class; // cache
- script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
-
-#ifndef NO_THREADS
- script_bind_lock->lock();
-#endif
+ ERR_FAIL_NULL_V(mono_object, false);
- void *data = (void *)script_bindings.insert(p_object, script_binding);
-
-#ifndef NO_THREADS
- script_bind_lock->unlock();
-#endif
+ r_script_binding.inited = true;
+ r_script_binding.type_name = type_name;
+ r_script_binding.wrapper_class = type_class; // cache
+ r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
+ r_script_binding.owner = p_object;
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(p_object);
@@ -1031,7 +1288,28 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
ref->reference();
}
- return data;
+ return true;
+}
+
+void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
+
+ SCOPED_MUTEX_LOCK(language_bind_mutex);
+
+ Map<Object *, CSharpScriptBinding>::Element *match = script_bindings.find(p_object);
+ if (match)
+ return (void *)match;
+
+ CSharpScriptBinding script_binding;
+
+ if (!setup_csharp_script_binding(script_binding, p_object))
+ return NULL;
+
+ return (void *)insert_script_binding(p_object, script_binding);
+}
+
+Map<Object *, CSharpScriptBinding>::Element *CSharpLanguage::insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding) {
+
+ return script_bindings.insert(p_object, p_script_binding);
}
void CSharpLanguage::free_instance_binding_data(void *p_data) {
@@ -1047,23 +1325,24 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
if (finalizing)
return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
-#ifndef NO_THREADS
- script_bind_lock->lock();
-#endif
+ {
+ SCOPED_MUTEX_LOCK(language_bind_mutex);
- Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
+ Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
- // Set the native instance field to IntPtr.Zero, if not yet garbage collected
- MonoObject *mono_object = data->value().gchandle->get_target();
- if (mono_object) {
- CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
- }
+ CSharpScriptBinding &script_binding = data->value();
- script_bindings.erase(data);
+ if (script_binding.inited) {
+ // Set the native instance field to IntPtr.Zero, if not yet garbage collected.
+ // This is done to avoid trying to dispose the native instance from Dispose(bool).
+ MonoObject *mono_object = script_binding.gchandle->get_target();
+ if (mono_object) {
+ CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
+ }
+ }
-#ifndef NO_THREADS
- script_bind_lock->unlock();
-#endif
+ script_bindings.erase(data);
+ }
}
void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
@@ -1075,9 +1354,13 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
#endif
void *data = p_object->get_script_instance_binding(get_language_index());
- if (!data)
+ CRASH_COND(!data);
+
+ CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
+ Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+
+ if (!script_binding.inited)
return;
- Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
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.
@@ -1103,14 +1386,18 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
CRASH_COND(!ref_owner);
#endif
+ 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();
- void *data = p_object->get_script_instance_binding(get_language_index());
- if (!data)
+ if (!script_binding.inited)
return refcount == 0;
- Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
- if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 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.
@@ -1154,6 +1441,10 @@ MonoObject *CSharpInstance::get_mono_object() const {
return gchandle->get_target();
}
+Object *CSharpInstance::get_owner() {
+ return owner;
+}
+
bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(!script.is_valid(), false);
@@ -1164,17 +1455,17 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
GDMonoClass *top = script->script_class;
while (top && top != script->native) {
- GDMonoField *field = script->script_class->get_field(p_name);
+ GDMonoField *field = top->get_field(p_name);
if (field) {
field->set_value_from_variant(mono_object, p_value);
return true;
}
- GDMonoProperty *property = script->script_class->get_property(p_name);
+ GDMonoProperty *property = top->get_property(p_name);
if (property) {
- property->set_value(mono_object, GDMonoMarshal::variant_to_mono_object(p_value));
+ property->set_value(mono_object, GDMonoMarshal::variant_to_mono_object(p_value, property->get_type()));
return true;
}
@@ -1194,7 +1485,7 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
MonoObject *ret = method->invoke(mono_object, args);
- if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret) == true)
+ if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret))
return true;
break;
@@ -1268,11 +1559,64 @@ 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()) {
p_properties->push_back(E->value());
}
+
+ // Call _get_property_list
+
+ ERR_FAIL_COND(!script.is_valid());
+
+ MonoObject *mono_object = get_mono_object();
+ ERR_FAIL_NULL(mono_object);
+
+ GDMonoClass *top = script->script_class;
+
+ while (top && top != script->native) {
+ GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get_property_list), 0);
+
+ if (method) {
+ MonoObject *ret = method->invoke(mono_object);
+
+ if (ret) {
+ Array array = Array(GDMonoMarshal::mono_object_to_variant(ret));
+ for (int i = 0, size = array.size(); i < size; i++)
+ p_properties->push_back(PropertyInfo::from_dict(array.get(i)));
+ return;
+ }
+
+ break;
+ }
+
+ top = top->get_parent_class();
+ }
}
Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const {
@@ -1407,19 +1751,15 @@ bool CSharpInstance::_unreference_owner_unsafe() {
if (!unsafe_referenced)
return false; // Already unreferenced
+ unsafe_referenced = false;
+
// Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance()
// Unsafe refcount decrement. The managed instance also counts as a reference.
// See: _reference_owner_unsafe()
- bool die = static_cast<Reference *>(owner)->unreference();
-
- if (die) {
- memdelete(owner);
- owner = NULL;
- }
-
- return die;
+ // Destroying the owner here means self destructing, so we defer the owner destruction to the caller.
+ return static_cast<Reference *>(owner)->unreference();
}
MonoObject *CSharpInstance::_internal_new_managed() {
@@ -1427,63 +1767,84 @@ MonoObject *CSharpInstance::_internal_new_managed() {
CRASH_COND(!gchandle.is_valid());
#endif
+ // Search the constructor first, to fail with an error if it's not found before allocating anything else.
+ GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
+ ERR_FAIL_NULL_V_MSG(ctor, NULL,
+ "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'.");
+
CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
ERR_FAIL_NULL_V(owner, NULL);
ERR_FAIL_COND_V(script.is_null(), NULL);
- if (base_ref)
- _reference_owner_unsafe();
-
- 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->set_script_instance(NULL);
- ERR_EXPLAIN("Failed to allocate memory for the object");
- ERR_FAIL_V(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_FAIL_V_MSG(NULL, "Failed to allocate memory for the object.");
}
+ // Tie managed to unmanaged
+ gchandle = MonoGCHandle::create_strong(mono_object);
+
+ if (base_ref)
+ _reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
+
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner);
// Construct
- GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
ctor->invoke_raw(mono_object, NULL);
- // Tie managed to unmanaged
- gchandle = MonoGCHandle::create_strong(mono_object);
-
return mono_object;
}
void CSharpInstance::mono_object_disposed(MonoObject *p_obj) {
#ifdef DEBUG_ENABLED
- CRASH_COND(base_ref == true);
+ CRASH_COND(base_ref);
CRASH_COND(gchandle.is_null());
#endif
CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
}
-void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted) {
+void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) {
#ifdef DEBUG_ENABLED
- CRASH_COND(base_ref == false);
+ CRASH_COND(!base_ref);
CRASH_COND(gchandle.is_null());
#endif
+
+ r_remove_script_instance = false;
+
if (_unreference_owner_unsafe()) {
- r_owner_deleted = true;
+ // Safe to self destruct here with memdelete(owner), but it's deferred to the caller to prevent future mistakes.
+ r_delete_owner = true;
} else {
- r_owner_deleted = false;
+ r_delete_owner = false;
CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
- if (p_is_finalizer) {
- // If the native instance is still alive, then it was
- // referenced from another thread before the finalizer could
- // unreference it and delete it, so we want to keep it.
- // GC.ReRegisterForFinalize(this) is not safe because the objects
- // referenced by this could have already been collected.
- // Instead we will create a new managed instance here.
- _internal_new_managed();
+
+ if (!p_is_finalizer) {
+ // If the native instance is still alive and Dispose() was called
+ // (instead of the finalizer), then we remove the script instance.
+ r_remove_script_instance = true;
+ } else if (!GDMono::get_singleton()->is_finalizing_scripts_domain()) {
+ // If the native instance is still alive and this is called from the finalizer,
+ // then it was referenced from another thread before the finalizer could
+ // unreference and delete it, so we want to keep it.
+ // GC.ReRegisterForFinalize(this) is not safe because the objects referenced by 'this'
+ // could have already been collected. Instead we will create a new managed instance here.
+ MonoObject *new_managed = _internal_new_managed();
+ if (!new_managed) {
+ r_remove_script_instance = true;
+ }
}
}
}
@@ -1537,7 +1898,7 @@ bool CSharpInstance::refcount_decremented() {
return ref_dying;
}
-MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(GDMonoClassMember *p_member) const {
+MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(IMonoClassMember *p_member) const {
if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute)))
return MultiplayerAPI::RPC_MODE_REMOTE;
@@ -1603,6 +1964,8 @@ void CSharpInstance::notification(int p_notification) {
// It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed
// to be sent at least once, which happens right before the call to the destructor.
+ predelete_notified = true;
+
if (base_ref) {
// It's not safe to proceed if the owner derives Reference and the refcount reached 0.
// At this point, Dispose() was already called (manually or from the finalizer) so
@@ -1618,10 +1981,8 @@ void CSharpInstance::notification(int p_notification) {
MonoObject *mono_object = get_mono_object();
ERR_FAIL_NULL(mono_object);
- GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
-
MonoException *exc = NULL;
- thunk(mono_object, (MonoObject **)&exc);
+ GDMonoUtils::dispose(mono_object, &exc);
if (exc) {
GDMonoUtils::set_pending_exception(exc);
@@ -1658,6 +2019,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;
@@ -1672,23 +2061,65 @@ CSharpInstance::CSharpInstance() :
owner(NULL),
base_ref(false),
ref_dying(false),
- unsafe_referenced(false) {
+ unsafe_referenced(false),
+ predelete_notified(false),
+ destructing_script_instance(false) {
}
CSharpInstance::~CSharpInstance() {
+ destructing_script_instance = true;
+
if (gchandle.is_valid()) {
- gchandle->release(); // Make sure it's released
+ if (!predelete_notified && !ref_dying) {
+ // This destructor is not called from the owners destructor.
+ // This could be being called from the owner's set_script_instance method,
+ // meaning this script is being replaced with another one. If this is the case,
+ // we must call Dispose here, because Dispose calls owner->set_script_instance(NULL)
+ // and that would mess up with the new script instance if called later.
+
+ MonoObject *mono_object = gchandle->get_target();
+
+ if (mono_object) {
+ MonoException *exc = NULL;
+ GDMonoUtils::dispose(mono_object, &exc);
+
+ if (exc) {
+ GDMonoUtils::set_pending_exception(exc);
+ }
+ }
+ }
+
+ gchandle->release(); // Make sure the gchandle is released
}
- if (base_ref && !ref_dying && owner) { // it may be called from the owner's destructor
- _unreference_owner_unsafe();
+ // If not being called from the owner's destructor, and we still hold a reference to the owner
+ if (base_ref && !ref_dying && owner && unsafe_referenced) {
+ // The owner's script or script instance is being replaced (or removed)
+
+ // Transfer ownership to an "instance binding"
+
+ void *data = owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
+ CRASH_COND(data == NULL);
+
+ CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
+
+ 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
}
if (script.is_valid() && owner) {
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->lock();
-#endif
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
#ifdef DEBUG_ENABLED
// CSharpInstance must not be created unless it's going to be added to the list for sure
@@ -1698,10 +2129,6 @@ CSharpInstance::~CSharpInstance() {
#else
script->instances.erase(owner);
#endif
-
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->unlock();
-#endif
}
}
@@ -1727,17 +2154,64 @@ 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() {
#ifdef TOOLS_ENABLED
- if (!valid) {
- for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) {
- E->get()->set_build_failed(true);
- }
+ if (!Engine::get_singleton()->is_editor_hint())
+ return false;
+
+ placeholder_fallback_enabled = true; // until proven otherwise
+
+ if (!valid)
return false;
- }
bool changed = false;
@@ -1752,16 +2226,20 @@ 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 create temporary MonoObject");
+ ERR_PRINT("Failed to allocate temporary MonoObject.");
return false;
}
uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed)
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
+
+ ERR_FAIL_NULL_V_MSG(ctor, NULL,
+ "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'.");
+
MonoException *ctor_exc = NULL;
ctor->invoke(tmp_object, NULL, &ctor_exc);
@@ -1785,18 +2263,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;
}
}
}
@@ -1806,25 +2284,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;
}
}
}
@@ -1834,10 +2312,8 @@ bool CSharpScript::_update_exports() {
// Dispose the temporary managed instance
- GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
-
MonoException *exc = NULL;
- thunk(tmp_object, (MonoObject **)&exc);
+ GDMonoUtils::dispose(tmp_object, &exc);
if (exc) {
ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
@@ -1848,6 +2324,8 @@ bool CSharpScript::_update_exports() {
tmp_object = NULL;
}
+ placeholder_fallback_enabled = false;
+
if (placeholders.size()) {
// Update placeholders if any
Map<StringName, Variant> values;
@@ -1855,7 +2333,6 @@ bool CSharpScript::_update_exports() {
_update_exports_values(values, propnames);
for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) {
- E->get()->set_build_failed(false);
E->get()->update(propnames, values);
}
}
@@ -1914,7 +2391,7 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve
arg.type = GDMonoMarshal::managed_to_variant_type(types[i]);
if (arg.type == Variant::NIL) {
- ERR_PRINTS("Unknown type of signal parameter: " + arg.name + " in " + p_class->get_full_name());
+ ERR_PRINTS("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'.");
return false;
}
@@ -1934,60 +2411,92 @@ 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, GDMonoClassMember *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;
- if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_FIELD) {
+ if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_FIELD) {
type = static_cast<GDMonoField *>(p_member)->get_type();
- } else if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_PROPERTY) {
+ } else if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) {
type = static_cast<GDMonoProperty *>(p_member)->get_type();
} else {
CRASH_NOW();
}
- Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
+ bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute));
- 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;
- }
-
- if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_PROPERTY) {
+ 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())) {
- 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);
+
+ ERR_FAIL_COND_V_MSG(hint_res == -1, false,
+ "Error while trying to determine information about the exported member: '" +
+ MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+
+ 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;
@@ -2000,12 +2509,12 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, GDMonoClassMember *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:
@@ -2014,46 +2523,62 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, GDMonoClassMember *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_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value.");
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_FAIL_COND_V_MSG(r_error, -1, "Failed to unbox '" + enum_field_name + "' constant enum value.");
- if (val != i) {
+ 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)) {
- hint = PROPERTY_HINT_RESOURCE_TYPE;
- hint_string = NATIVE_GDMONOCLASS_NAME(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);
+
+ 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;
+
+ ERR_FAIL_COND_V_MSG(elem_variant_type == Variant::NIL, -1, "Unknown array element type.");
+
+ int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string);
+
+ ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type.");
+
+ // 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
@@ -2136,37 +2661,63 @@ void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const {
void CSharpScript::_bind_methods() {
- ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo(Variant::OBJECT, "new"));
+ ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new"));
}
-Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class) {
+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 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 = GDMonoUtils::get_class_native_base(script->script_class);
+ initialize_for_managed_type(script, p_class, p_native);
- CRASH_COND(script->native == NULL);
+ return script;
+}
+
+void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) {
+
+ // This method should not fail, only assertions allowed
+
+ CRASH_COND(p_class == NULL);
+
+ 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;
- GDMonoClass *base = script->script_class->get_parent_class();
+ p_script->valid = true;
+ p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
- if (base != script->native)
- script->base = base;
+ 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;
@@ -2176,18 +2727,19 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class) {
}
#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 {
@@ -2195,23 +2747,39 @@ 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",
ProjectSettings::get_singleton()->globalize_path(get_path()));
} else {
- ERR_PRINTS("Cannot add " + get_path() + " to the C# project because it could not be created.");
+ ERR_PRINTS("C# project could not be created; cannot add file: '" + get_path() + "'.");
}
}
}
#endif
#ifdef TOOLS_ENABLED
- return valid && (tool || ScriptServer::is_scripting_enabled());
+ bool extra_cond = tool || ScriptServer::is_scripting_enabled();
#else
- return valid;
+ bool extra_cond = true;
#endif
+
+ // FIXME Need to think this through better.
+ // For tool scripts, this will never fire if the class is not found. That's because we
+ // don't know if it's a tool script if we can't find the class to access the attributes.
+ if (extra_cond && !script_class) {
+ if (GDMono::get_singleton()->get_project_assembly() == NULL) {
+ // The project assembly is not loaded
+ ERR_FAIL_V_MSG(NULL, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'.");
+ } else {
+ // The project assembly is loaded, but the class could not found
+ ERR_FAIL_V_MSG(NULL, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'.");
+ }
+ }
+
+ return valid && extra_cond;
}
StringName CSharpScript::get_instance_base_type() const {
@@ -2226,52 +2794,86 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
/* STEP 1, CREATE */
+ // Search the constructor first, to fail with an error if it's not found before allocating anything else.
+ GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
+ if (ctor == NULL) {
+ ERR_FAIL_COND_V_MSG(p_argcount == 0, NULL,
+ "Cannot create script instance. The class '" + script_class->get_full_name() +
+ "' does not define a parameterless constructor." +
+ (get_path().empty() ? String() : " Path: '" + get_path() + "'."));
+
+ ERR_FAIL_V_MSG(NULL, "Constructor not found.");
+ }
+
+ Ref<Reference> ref;
+ if (p_isref) {
+ // Hold it alive. Important if we have to dispose a script instance binding before creating the CSharpInstance.
+ ref = Ref<Reference>(static_cast<Reference *>(p_owner));
+ }
+
+ // If the object had a script instance binding, dispose it before adding the CSharpInstance
+ if (p_owner->has_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index())) {
+ void *data = p_owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
+ CRASH_COND(data == NULL);
+
+ CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
+ if (script_binding.inited && script_binding.gchandle.is_valid()) {
+ MonoObject *mono_object = script_binding.gchandle->get_target();
+ if (mono_object) {
+ MonoException *exc = NULL;
+ GDMonoUtils::dispose(mono_object, &exc);
+
+ if (exc) {
+ GDMonoUtils::set_pending_exception(exc);
+ }
+ }
+
+ script_binding.inited = false;
+ }
+ }
+
CSharpInstance *instance = memnew(CSharpInstance);
instance->base_ref = p_isref;
instance->script = Ref<CSharpScript>(this);
instance->owner = p_owner;
instance->owner->set_script_instance(instance);
- if (instance->base_ref)
- instance->_reference_owner_unsafe();
-
/* 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
instance->script = Ref<CSharpScript>();
- instance->owner->set_script_instance(NULL);
+ instance->owner = NULL;
+
+ bool die = instance->_unreference_owner_unsafe();
+ // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug.
+ CRASH_COND(die == true);
+
+ p_owner->set_script_instance(NULL);
r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
- ERR_EXPLAIN("Failed to allocate memory for the object");
- ERR_FAIL_V(NULL);
+ ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object.");
}
- uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(mono_object); // we might lock after this, so pin it
-
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->lock();
-#endif
+ // Tie managed to unmanaged
+ instance->gchandle = MonoGCHandle::create_strong(mono_object);
- instances.insert(instance->owner);
+ if (instance->base_ref)
+ instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->unlock();
-#endif
+ {
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ instances.insert(instance->owner);
+ }
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner);
// Construct
- GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
ctor->invoke(mono_object, p_args);
- // Tie managed to unmanaged
- instance->gchandle = MonoGCHandle::create_strong(mono_object);
-
/* STEP 3, PARTY */
- MonoGCHandle::free_handle(pinned_gchandle);
-
//@TODO make thread safe
return instance;
}
@@ -2285,11 +2887,10 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call
r_error.error = Variant::CallError::CALL_OK;
REF ref;
- Object *owner = NULL;
ERR_FAIL_NULL_V(native, Variant());
- owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native));
+ Object *owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native));
Reference *r = Object::cast_to<Reference>(owner);
if (r) {
@@ -2317,33 +2918,19 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
CRASH_COND(!valid);
#endif
- if (!script_class) {
- if (GDMono::get_singleton()->get_project_assembly() == NULL) {
- // The project assembly is not loaded
- ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path());
- ERR_FAIL_V(NULL);
- } else {
- // The project assembly is loaded, but the class could not found
- ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path());
- ERR_FAIL_V(NULL);
- }
- }
-
- ERR_FAIL_COND_V(!valid, NULL);
-
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() + "'");
}
- ERR_EXPLAIN("Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
- ERR_FAIL_V(NULL);
+ ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + native_name +
+ "', so it can't be instanced in object of type: '" + p_this->get_class() + "'.");
}
}
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) {
@@ -2360,17 +2947,8 @@ PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_t
bool CSharpScript::instance_has(const Object *p_this) const {
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->lock();
-#endif
-
- bool ret = instances.has((Object *)p_this);
-
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->unlock();
-#endif
-
- return ret;
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ return instances.has((Object *)p_this);
}
bool CSharpScript::has_source_code() const {
@@ -2393,6 +2971,18 @@ void CSharpScript::set_source_code(const String &p_code) {
#endif
}
+void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
+
+ if (!script_class)
+ return;
+
+ // TODO: Filter out things unsuitable for explicit calls, like constructors.
+ const Vector<GDMonoMethod *> &methods = script_class->get_all_methods();
+ for (int i = 0; i < methods.size(); ++i) {
+ p_list->push_back(methods[i]->get_method_info());
+ }
+}
+
bool CSharpScript::has_method(const StringName &p_method) const {
if (!script_class)
@@ -2401,34 +2991,76 @@ bool CSharpScript::has_method(const StringName &p_method) const {
return script_class->has_fetched_method_unknown_params(p_method);
}
-Error CSharpScript::reload(bool p_keep_state) {
+MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->lock();
-#endif
+ if (!script_class)
+ return MethodInfo();
- bool has_instances = instances.size();
+ GDMonoClass *top = script_class;
-#ifndef NO_THREADS
- CSharpLanguage::singleton->lock->unlock();
-#endif
+ while (top && top != native) {
+ GDMonoMethod *params = top->get_fetched_method_unknown_params(p_method);
+ if (params) {
+ return params->get_method_info();
+ }
+
+ top = top->get_parent_class();
+ }
+
+ return MethodInfo();
+}
+
+Error CSharpScript::reload(bool p_keep_state) {
+
+ bool has_instances;
+ {
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ has_instances = instances.size();
+ }
ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE);
GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();
if (project_assembly) {
- script_class = project_assembly->get_object_derived_class(name);
+ const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path());
+ if (script_metadata_var) {
+ Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"];
+ const Variant *namespace_ = script_metadata.getptr("namespace");
+ const Variant *class_name = script_metadata.getptr("class_name");
+ ERR_FAIL_NULL_V(namespace_, ERR_BUG);
+ ERR_FAIL_NULL_V(class_name, ERR_BUG);
+ GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String());
+ if (klass) {
+ bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(klass);
+ ERR_FAIL_COND_V(!obj_type, ERR_BUG);
+ script_class = klass;
+ }
+ } else {
+ // Missing script metadata. Fallback to legacy method
+ script_class = project_assembly->get_object_derived_class(name);
+ }
valid = script_class != NULL;
if (script_class) {
#ifdef DEBUG_ENABLED
- print_verbose("Found class " + script_class->get_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);
@@ -2466,6 +3098,7 @@ Error CSharpScript::reload(bool p_keep_state) {
}
load_script_signals(script_class, native);
+ _update_exports();
}
return OK;
@@ -2505,10 +3138,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 {
@@ -2546,28 +3176,13 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
Error CSharpScript::load_source_code(const String &p_path) {
- PoolVector<uint8_t> sourcef;
- Error err;
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V(err != OK, err);
-
- int len = f->get_len();
- sourcef.resize(len + 1);
- PoolVector<uint8_t>::Write w = sourcef.write();
- int r = f->get_buffer(w.ptr(), len);
- f->close();
- memdelete(f);
- ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN);
- w[len] = 0;
-
- String s;
- if (s.parse_utf8((const char *)w.ptr())) {
-
- ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
- ERR_FAIL_V(ERR_INVALID_DATA);
- }
+ Error ferr = read_all_file_utf8(p_path, source);
- source = s;
+ ERR_FAIL_COND_V_MSG(ferr != OK, ferr,
+ ferr == ERR_INVALID_DATA ?
+ "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded."
+ " Please ensure that scripts are saved in valid UTF-8 unicode." :
+ "Failed to read file: '" + p_path + "'.");
#ifdef TOOLS_ENABLED
source_changed_cache = true;
@@ -2588,6 +3203,7 @@ CSharpScript::CSharpScript() :
#ifdef TOOLS_ENABLED
source_changed_cache = false;
+ placeholder_fallback_enabled = false;
exports_invalidated = true;
#endif
@@ -2596,35 +3212,19 @@ CSharpScript::CSharpScript() :
_resource_path_changed();
#ifdef DEBUG_ENABLED
-
-#ifndef NO_THREADS
- CSharpLanguage::get_singleton()->lock->lock();
-#endif
-
- CSharpLanguage::get_singleton()->script_list.add(&script_list);
-
-#ifndef NO_THREADS
- CSharpLanguage::get_singleton()->lock->unlock();
+ {
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ CSharpLanguage::get_singleton()->script_list.add(&this->script_list);
+ }
#endif
-
-#endif // DEBUG_ENABLED
}
CSharpScript::~CSharpScript() {
#ifdef DEBUG_ENABLED
-
-#ifndef NO_THREADS
- CSharpLanguage::get_singleton()->lock->lock();
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ CSharpLanguage::get_singleton()->script_list.remove(&this->script_list);
#endif
-
- CSharpLanguage::get_singleton()->script_list.remove(&script_list);
-
-#ifndef NO_THREADS
- CSharpLanguage::get_singleton()->lock->unlock();
-#endif
-
-#endif // DEBUG_ENABLED
}
/*************** RESOURCE ***************/
@@ -2642,7 +3242,7 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED)
Error err = script->load_source_code(p_path);
- ERR_FAIL_COND_V(err != OK, RES());
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load C# script file '" + p_path + "'.");
#endif
script->set_path(p_original_path);
@@ -2651,14 +3251,14 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#ifdef DEBUG_ENABLED
// User is responsible for thread attach/detach
- ERR_EXPLAIN("Thread is not attached");
- CRASH_COND(mono_domain_get() == NULL);
+ CRASH_COND_MSG(mono_domain_get() == NULL, "Thread is not attached.");
#endif
#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());
@@ -2666,8 +3266,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);
@@ -2718,14 +3318,14 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
"Compile",
ProjectSettings::get_singleton()->globalize_path(p_path));
} else {
- ERR_PRINTS("Cannot add " + p_path + " to the C# project because it could not be created.");
+ ERR_PRINTS("C# project could not be created; cannot add file: '" + p_path + "'.");
}
}
#endif
Error err;
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V(err, err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save C# script file '" + p_path + "'.");
file->store_string(source);
@@ -2737,9 +3337,11 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
file->close();
memdelete(file);
+#ifdef TOOLS_ENABLED
if (ScriptServer::is_reload_scripts_on_save_enabled()) {
CSharpLanguage::get_singleton()->reload_tool_script(p_resource, false);
}
+#endif
return OK;
}
@@ -2761,7 +3363,10 @@ CSharpLanguage::StringNameCache::StringNameCache() {
_signal_callback = StaticCString::create("_signal_callback");
_set = StaticCString::create("_set");
_get = StaticCString::create("_get");
+ _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 8b0a095890..eb168f344d 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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,11 +67,11 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
class CSharpScript : public Script {
- GDCLASS(CSharpScript, Script)
+ GDCLASS(CSharpScript, Script);
friend class CSharpInstance;
friend class CSharpLanguage;
- friend class CSharpScriptDepSort;
+ friend struct CSharpScriptDepSort;
bool tool;
bool valid;
@@ -82,6 +86,20 @@ class CSharpScript : public Script {
Set<Object *> instances;
+#ifdef GD_MONO_HOT_RELOAD
+ struct StateBackup {
+ // TODO
+ // Replace with buffer containing the serialized state of managed scripts.
+ // Keep variant state backup to use only with script instance placeholders.
+ List<Pair<StringName, Variant> > properties;
+ };
+
+ 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;
StringName name;
@@ -100,15 +118,13 @@ class CSharpScript : public Script {
Map<StringName, Variant> exported_members_defval_cache; // member_default_values_cache
Set<PlaceHolderScriptInstance *> placeholders;
bool source_changed_cache;
+ bool placeholder_fallback_enabled;
bool exports_invalidated;
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
-#ifdef DEBUG_ENABLED
- Map<ObjectID, List<Pair<StringName, Variant> > > pending_reload_state;
-#endif
-
Map<StringName, PropertyInfo> member_info;
void _clear();
@@ -118,7 +134,8 @@ class CSharpScript : public Script {
bool _update_exports();
#ifdef TOOLS_ENABLED
- bool _get_member_export(GDMonoClass *p_class, GDMonoClassMember *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);
@@ -126,7 +143,8 @@ 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);
+ 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();
@@ -153,20 +171,26 @@ public:
virtual bool has_script_signal(const StringName &p_signal) const;
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const;
- /* TODO */ virtual bool get_property_default_value(const StringName &p_property, Variant &r_value) const;
+ virtual bool get_property_default_value(const StringName &p_property, Variant &r_value) const;
virtual void get_script_property_list(List<PropertyInfo> *p_list) const;
virtual void update_exports();
virtual bool is_tool() const { return tool; }
+ virtual bool is_valid() const { return valid; }
+
virtual Ref<Script> get_base_script() const;
virtual ScriptLanguage *get_language() const;
- /* TODO */ virtual void get_script_method_list(List<MethodInfo> *p_list) const {}
+ virtual void get_script_method_list(List<MethodInfo> *p_list) const;
bool has_method(const StringName &p_method) const;
- /* TODO */ MethodInfo get_method_info(const StringName &p_method) const { return MethodInfo(); }
+ MethodInfo get_method_info(const StringName &p_method) const;
virtual int get_member_line(const StringName &p_member) const;
+#ifdef TOOLS_ENABLED
+ virtual bool is_placeholder_fallback_enabled() const { return placeholder_fallback_enabled; }
+#endif
+
Error load_source_code(const String &p_path);
StringName get_script_name() const;
@@ -184,13 +208,22 @@ class CSharpInstance : public ScriptInstance {
bool base_ref;
bool ref_dying;
bool unsafe_referenced;
+ bool predelete_notified;
+ bool destructing_script_instance;
Ref<CSharpScript> script;
Ref<MonoGCHandle> gchandle;
bool _reference_owner_unsafe();
+
+ /*
+ * If true is returned, the caller must memdelete the script instance's owner.
+ */
bool _unreference_owner_unsafe();
+ /*
+ * If NULL is returned, the caller must destroy the script instance by removing it from its owner.
+ */
MonoObject *_internal_new_managed();
// Do not use unless you know what you are doing
@@ -199,11 +232,17 @@ class CSharpInstance : public ScriptInstance {
void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
- MultiplayerAPI::RPCMode _member_get_rpc_mode(GDMonoClassMember *p_member) const;
+ 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;
+ _FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; }
+
+ virtual Object *get_owner();
+
virtual bool set(const StringName &p_name, const Variant &p_value);
virtual bool get(const StringName &p_name, Variant &r_ret) const;
virtual void get_property_list(List<PropertyInfo> *p_properties) const;
@@ -216,7 +255,12 @@ public:
virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
void mono_object_disposed(MonoObject *p_obj);
- void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted);
+
+ /*
+ * If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, if
+ * 'r_remove_script_instance' is set to true, the caller must destroy the script instance by removing it from its owner.
+ */
+ void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance);
virtual void refcount_incremented();
virtual bool refcount_decremented();
@@ -227,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();
@@ -236,9 +282,11 @@ public:
};
struct CSharpScriptBinding {
+ bool inited;
StringName type_name;
GDMonoClass *wrapper_class;
Ref<MonoGCHandle> gchandle;
+ Object *owner;
};
class CSharpLanguage : public ScriptLanguage {
@@ -253,11 +301,9 @@ class CSharpLanguage : public ScriptLanguage {
GDMono *gdmono;
SelfList<CSharpScript>::List script_list;
- Mutex *lock;
- Mutex *script_bind_lock;
- Mutex *script_gchandle_release_lock;
-
- Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload;
+ Mutex *script_instances_mutex;
+ Mutex *script_gchandle_release_mutex;
+ Mutex *language_bind_mutex;
Map<Object *, CSharpScriptBinding> script_bindings;
@@ -266,18 +312,42 @@ class CSharpLanguage : public ScriptLanguage {
StringName _signal_callback;
StringName _set;
StringName _get;
+ StringName _get_property_list;
StringName _notification;
StringName _script_source;
StringName dotctor; // .ctor
+ StringName on_before_serialize; // OnBeforeSerialize
+ StringName on_after_deserialize; // OnAfterDeserialize
StringNameCache();
};
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 _on_scripts_domain_unloaded();
+
+#ifdef TOOLS_ENABLED
+ EditorPlugin *godotsharp_editor;
+
+ static void _editor_init_callback();
+#endif
+
public:
StringNameCache string_names;
+ Mutex *get_language_bind_mutex() { return language_bind_mutex; }
+
_FORCE_INLINE_ int get_language_index() { return lang_idx; }
void set_language_index(int p_idx);
@@ -285,16 +355,31 @@ 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);
-#ifdef TOOLS_ENABLED
- void reload_assemblies_if_needed(bool p_soft_reload);
+#ifdef GD_MONO_HOT_RELOAD
+ bool is_assembly_reloading_needed();
+ void reload_assemblies(bool p_soft_reload);
#endif
+ _FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() {
+ return scripts_metadata_invalidated ? Dictionary() : scripts_metadata;
+ }
+
+ _FORCE_INLINE_ const Dictionary &get_scripts_metadata() {
+ if (scripts_metadata_invalidated)
+ _load_scripts_metadata();
+ return scripts_metadata;
+ }
+
virtual String get_name() const;
/* LANGUAGE FUNCTIONS */
@@ -318,17 +403,16 @@ 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) {}
/* DEBUGGER FUNCTIONS */
- /* TODO */ virtual String debug_get_error() const { return ""; }
- /* TODO */ virtual int debug_get_stack_level_count() const { return 1; }
- /* TODO */ virtual int debug_get_stack_level_line(int p_level) const { return 1; }
- /* TODO */ virtual String debug_get_stack_level_function(int p_level) const { return ""; }
- /* TODO */ virtual String debug_get_stack_level_source(int p_level) const { return ""; }
+ virtual String debug_get_error() const;
+ virtual int debug_get_stack_level_count() const;
+ virtual int debug_get_stack_level_line(int p_level) const;
+ virtual String debug_get_stack_level_function(int p_level) const;
+ virtual String debug_get_stack_level_source(int p_level) const;
/* TODO */ virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
/* TODO */ virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
/* TODO */ virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
@@ -367,6 +451,9 @@ public:
virtual void refcount_incremented_instance_binding(Object *p_object);
virtual bool refcount_decremented_instance_binding(Object *p_object);
+ Map<Object *, CSharpScriptBinding>::Element *insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding);
+ bool setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object);
+
#ifdef DEBUG_ENABLED
Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace);
#endif
diff --git a/modules/mono/doc_classes/@C#.xml b/modules/mono/doc_classes/@C#.xml
index 082bc30fd8..826c106d7e 100644
--- a/modules/mono/doc_classes/@C#.xml
+++ b/modules/mono/doc_classes/@C#.xml
@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="@C#" category="Core" version="3.1">
+<class name="@C#" category="Core" version="3.2">
<brief_description>
</brief_description>
<description>
</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 a1f7399653..de2e246ea9 100644
--- a/modules/mono/doc_classes/CSharpScript.xml
+++ b/modules/mono/doc_classes/CSharpScript.xml
@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="CSharpScript" inherits="Script" category="Core" version="3.1">
+<class name="CSharpScript" inherits="Script" category="Core" version="3.2">
<brief_description>
</brief_description>
<description>
</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 985c66464b..18556a84ba 100644
--- a/modules/mono/doc_classes/GodotSharp.xml
+++ b/modules/mono/doc_classes/GodotSharp.xml
@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="GodotSharp" inherits="Object" category="Core" version="3.1">
+<class name="GodotSharp" inherits="Object" category="Core" version="3.2">
<brief_description>
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
- <demos>
- </demos>
<methods>
<method name="attach_thread">
<return type="void">
@@ -23,18 +21,44 @@
Detaches the current thread from the mono runtime.
</description>
</method>
- <method name="is_domain_loaded">
+ <method name="get_domain_id">
+ <return type="int">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="get_scripts_domain_id">
+ <return type="int">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="is_domain_finalizing_for_unload">
+ <return type="bool">
+ </return>
+ <argument index="0" name="domain_id" type="int">
+ </argument>
+ <description>
+ Returns whether the domain is being finalized.
+ </description>
+ </method>
+ <method name="is_runtime_initialized">
+ <return type="bool">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="is_runtime_shutting_down">
<return type="bool">
</return>
<description>
- Returns whether the scripts domain is loaded.
</description>
</method>
- <method name="is_finalizing_domain">
+ <method name="is_scripts_domain_loaded">
<return type="bool">
</return>
<description>
- Returns whether the scripts domain is being finalized.
+ Returns whether the scripts domain is loaded.
</description>
</method>
</methods>
diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs
deleted file mode 100644
index 16beacb45c..0000000000
--- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs
+++ /dev/null
@@ -1,430 +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_FrameworkPath();
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir();
- [MethodImpl(MethodImplOptions.InternalCall)]
- private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows();
-
- 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 GetFrameworkPath()
- {
- return godot_icall_BuildInstance_get_FrameworkPath();
- }
-
- 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 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)
- {
- bool debugMSBuild = IsDebugMSBuildRequested();
-
- List<string> customPropertiesList = new List<string>();
-
- if (customProperties != null)
- customPropertiesList.AddRange(customProperties);
-
- string frameworkPath = GetFrameworkPath();
-
- if (!string.IsNullOrEmpty(frameworkPath))
- customPropertiesList.Add("FrameworkPathOverride=" + frameworkPath);
-
- string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
-
- ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
-
- bool redirectOutput = !debugMSBuild;
-
- 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)
- {
- bool debugMSBuild = IsDebugMSBuildRequested();
-
- if (process != null)
- throw new InvalidOperationException("Already in use");
-
- List<string> customPropertiesList = new List<string>();
-
- if (customProperties != null)
- customPropertiesList.AddRange(customProperties);
-
- string frameworkPath = GetFrameworkPath();
-
- if (!string.IsNullOrEmpty(frameworkPath))
- customPropertiesList.Add("FrameworkPathOverride=" + frameworkPath);
-
- string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
-
- ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
-
- bool redirectOutput = !debugMSBuild;
-
- 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(';');
-
- 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/MonoDevelopInstance.cs b/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs
deleted file mode 100644
index fba4a8f65c..0000000000
--- a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-using System;
-using System.IO;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Runtime.CompilerServices;
-
-namespace GodotSharpTools.Editor
-{
- public class MonoDevelopInstance
- {
- public enum EditorId
- {
- MonoDevelop = 0,
- VisualStudioForMac = 1
- }
-
- readonly string solutionFile;
- readonly EditorId editorId;
-
- Process process;
-
- public void Execute(string[] files)
- {
- bool newWindow = process == null || process.HasExited;
-
- List<string> args = new List<string>();
-
- string command;
-
- if (Utils.OS.IsOSX())
- {
- string bundleId = codeEditorBundleIds[editorId];
-
- if (IsApplicationBundleInstalled(bundleId))
- {
- command = "open";
-
- args.Add("-b");
- args.Add(bundleId);
-
- // The 'open' process must wait until the application finishes
- if (newWindow)
- args.Add("--wait-apps");
-
- args.Add("--args");
- }
- else
- {
- command = codeEditorPaths[editorId];
- }
- }
- else
- {
- command = codeEditorPaths[editorId];
- }
-
- args.Add("--ipc-tcp");
-
- if (newWindow)
- args.Add("\"" + Path.GetFullPath(solutionFile) + "\"");
-
- foreach (var file in files)
- {
- int semicolonIndex = file.IndexOf(';');
-
- string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex);
- string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex);
-
- args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\"");
- }
-
- if (newWindow)
- {
- process = Process.Start(new ProcessStartInfo()
- {
- FileName = command,
- Arguments = string.Join(" ", args),
- UseShellExecute = false
- });
- }
- else
- {
- Process.Start(new ProcessStartInfo()
- {
- FileName = command,
- Arguments = string.Join(" ", args),
- UseShellExecute = false
- });
- }
- }
-
- public MonoDevelopInstance(string solutionFile, EditorId editorId)
- {
- if (editorId == EditorId.VisualStudioForMac && !Utils.OS.IsOSX())
- throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform");
-
- this.solutionFile = solutionFile;
- 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;
-
- static MonoDevelopInstance()
- {
- if (Utils.OS.IsOSX())
- {
- codeEditorPaths = new Dictionary<EditorId, string>
- {
- // Rely on PATH
- { EditorId.MonoDevelop, "monodevelop" },
- { EditorId.VisualStudioForMac, "VisualStudio" }
- };
- codeEditorBundleIds = new Dictionary<EditorId, string>
- {
- // TODO EditorId.MonoDevelop
- { EditorId.VisualStudioForMac, "com.microsoft.visual-studio" }
- };
- }
- else if (Utils.OS.IsWindows())
- {
- 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" }
- };
- }
- else if (Utils.OS.IsUnix())
- {
- codeEditorPaths = new Dictionary<EditorId, string>
- {
- // Rely on PATH
- { EditorId.MonoDevelop, "monodevelop" }
- };
- }
- }
- }
-}
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/Project/ProjectUtils.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs
deleted file mode 100644
index 6889ea715f..0000000000
--- a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.IO;
-using Microsoft.Build.Construction;
-
-namespace GodotSharpTools.Project
-{
- public static class ProjectUtils
- {
- public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
- {
- var dir = Directory.GetParent(projectPath).FullName;
- var root = ProjectRootElement.Open(projectPath);
- if (root.AddItemChecked(itemType, include.RelativeToPath(dir).Replace("/", "\\")))
- root.Save();
- }
- }
-}
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..dcfdd83831
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
@@ -0,0 +1,60 @@
+<?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>
+ <LangVersion>7</LangVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>portable</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>portable</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="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/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
index 773e8196f7..24c7cb1573 100644
--- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
@@ -1,17 +1,18 @@
-<?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>
+ <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
<OutputType>Library</OutputType>
- <RootNamespace>GodotSharpTools</RootNamespace>
- <AssemblyName>GodotSharpTools</AssemblyName>
+ <RootNamespace>GodotTools.Core</RootNamespace>
+ <AssemblyName>GodotTools.Core</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <LangVersion>7</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
+ <DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
@@ -20,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>
@@ -29,18 +29,11 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
- <Reference Include="Microsoft.Build" />
- <Reference Include="Microsoft.Build.Framework" />
</ItemGroup>
<ItemGroup>
- <Compile Include="StringExtensions.cs" />
- <Compile Include="Build\BuildSystem.cs" />
- <Compile Include="Editor\MonoDevelopInstance.cs" />
- <Compile Include="Project\ProjectExtensions.cs" />
- <Compile Include="Project\ProjectGenerator.cs" />
- <Compile Include="Project\ProjectUtils.cs" />
+ <Compile Include="ProcessExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="Utils\OS.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 b66c86f8ce..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();
@@ -36,17 +37,41 @@ namespace GodotSharpTools
public static bool IsAbsolutePath(this string path)
{
- return path.StartsWith("/") || path.StartsWith("\\") || path.StartsWith(driveRoot);
+ return path.StartsWith("/", StringComparison.Ordinal) ||
+ path.StartsWith("\\", StringComparison.Ordinal) ||
+ path.StartsWith(driveRoot, StringComparison.Ordinal);
}
public static string CsvEscape(this string value, char delimiter = ',')
{
- bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
+ 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.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
new file mode 100644
index 0000000000..7a2ff2ca56
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace GodotTools.IdeConnection
+{
+ public class ConsoleLogger : ILogger
+ {
+ public void LogDebug(string message)
+ {
+ Console.WriteLine("DEBUG: " + message);
+ }
+
+ public void LogInfo(string message)
+ {
+ Console.WriteLine("INFO: " + message);
+ }
+
+ public void LogWarning(string message)
+ {
+ Console.WriteLine("WARN: " + message);
+ }
+
+ public void LogError(string message)
+ {
+ Console.WriteLine("ERROR: " + message);
+ }
+
+ public void LogError(string message, Exception e)
+ {
+ Console.WriteLine("EXCEPTION: " + message);
+ Console.WriteLine(e);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
new file mode 100644
index 0000000000..be89638241
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
@@ -0,0 +1,94 @@
+using System;
+using Path = System.IO.Path;
+
+namespace GodotTools.IdeConnection
+{
+ public class GodotIdeBase : IDisposable
+ {
+ private ILogger logger;
+
+ public ILogger Logger
+ {
+ get => logger ?? (logger = new ConsoleLogger());
+ set => logger = value;
+ }
+
+ private readonly string projectMetadataDir;
+
+ protected const string MetaFileName = "ide_server_meta.txt";
+ protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName);
+
+ private GodotIdeConnection connection;
+ protected readonly object ConnectionLock = new object();
+
+ public bool IsDisposed { get; private set; } = false;
+
+ public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected;
+
+ public event Action Connected
+ {
+ add
+ {
+ if (connection != null && !connection.IsDisposed)
+ connection.Connected += value;
+ }
+ remove
+ {
+ if (connection != null && !connection.IsDisposed)
+ connection.Connected -= value;
+ }
+ }
+
+ protected GodotIdeConnection Connection
+ {
+ get => connection;
+ set
+ {
+ connection?.Dispose();
+ connection = value;
+ }
+ }
+
+ protected GodotIdeBase(string projectMetadataDir)
+ {
+ this.projectMetadataDir = projectMetadataDir;
+ }
+
+ protected void DisposeConnection()
+ {
+ lock (ConnectionLock)
+ {
+ connection?.Dispose();
+ }
+ }
+
+ ~GodotIdeBase()
+ {
+ Dispose(disposing: false);
+ }
+
+ public void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ lock (ConnectionLock)
+ {
+ if (IsDisposed) // lock may not be fair
+ return;
+ IsDisposed = true;
+ }
+
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ connection?.Dispose();
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
new file mode 100644
index 0000000000..4f56a8d71b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
@@ -0,0 +1,219 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace GodotTools.IdeConnection
+{
+ public abstract class GodotIdeClient : GodotIdeBase
+ {
+ protected GodotIdeMetadata GodotIdeMetadata;
+
+ private readonly FileSystemWatcher fsWatcher;
+
+ protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir)
+ {
+ messageHandlers = InitializeMessageHandlers();
+
+ // FileSystemWatcher requires an existing directory
+ if (!File.Exists(projectMetadataDir))
+ Directory.CreateDirectory(projectMetadataDir);
+
+ fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName);
+ }
+
+ private void OnMetaFileChanged(object sender, FileSystemEventArgs e)
+ {
+ if (IsDisposed)
+ return;
+
+ lock (ConnectionLock)
+ {
+ if (IsDisposed)
+ return;
+
+ if (!File.Exists(MetaFilePath))
+ return;
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null && metadata != GodotIdeMetadata)
+ {
+ GodotIdeMetadata = metadata.Value;
+ ConnectToServer();
+ }
+ }
+ }
+
+ private void OnMetaFileDeleted(object sender, FileSystemEventArgs e)
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected)
+ DisposeConnection();
+
+ // The file may have been re-created
+
+ lock (ConnectionLock)
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected || !File.Exists(MetaFilePath))
+ return;
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null)
+ {
+ GodotIdeMetadata = metadata.Value;
+ ConnectToServer();
+ }
+ }
+ }
+
+ private GodotIdeMetadata? ReadMetadataFile()
+ {
+ using (var reader = File.OpenText(MetaFilePath))
+ {
+ string portStr = reader.ReadLine();
+
+ if (portStr == null)
+ return null;
+
+ string editorExecutablePath = reader.ReadLine();
+
+ if (editorExecutablePath == null)
+ return null;
+
+ if (!int.TryParse(portStr, out int port))
+ return null;
+
+ return new GodotIdeMetadata(port, editorExecutablePath);
+ }
+ }
+
+ private void ConnectToServer()
+ {
+ var tcpClient = new TcpClient();
+
+ Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage);
+ Connection.Logger = Logger;
+
+ try
+ {
+ Logger.LogInfo("Connecting to Godot Ide Server");
+
+ tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port);
+
+ Logger.LogInfo("Connection open with Godot Ide Server");
+
+ var clientThread = new Thread(Connection.Start)
+ {
+ IsBackground = true,
+ Name = "Godot Ide Connection Client"
+ };
+ clientThread.Start();
+ }
+ catch (SocketException e)
+ {
+ if (e.SocketErrorCode == SocketError.ConnectionRefused)
+ Logger.LogError("The connection to the Godot Ide Server was refused");
+ else
+ throw;
+ }
+ }
+
+ public void Start()
+ {
+ Logger.LogInfo("Starting Godot Ide Client");
+
+ fsWatcher.Changed += OnMetaFileChanged;
+ fsWatcher.Deleted += OnMetaFileDeleted;
+ fsWatcher.EnableRaisingEvents = true;
+
+ lock (ConnectionLock)
+ {
+ if (IsDisposed)
+ return;
+
+ if (!File.Exists(MetaFilePath))
+ {
+ Logger.LogInfo("There is no Godot Ide Server running");
+ return;
+ }
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null)
+ {
+ GodotIdeMetadata = metadata.Value;
+ ConnectToServer();
+ }
+ else
+ {
+ Logger.LogError("Failed to read Godot Ide metadata file");
+ }
+ }
+ }
+
+ public bool WriteMessage(Message message)
+ {
+ return Connection.WriteMessage(message);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ fsWatcher?.Dispose();
+ }
+ }
+
+ protected virtual bool HandleMessage(Message message)
+ {
+ if (messageHandlers.TryGetValue(message.Id, out var action))
+ {
+ action(message.Arguments);
+ return true;
+ }
+
+ return false;
+ }
+
+ private readonly Dictionary<string, Action<string[]>> messageHandlers;
+
+ private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
+ {
+ return new Dictionary<string, Action<string[]>>
+ {
+ ["OpenFile"] = args =>
+ {
+ switch (args.Length)
+ {
+ case 1:
+ OpenFile(file: args[0]);
+ return;
+ case 2:
+ OpenFile(file: args[0], line: int.Parse(args[1]));
+ return;
+ case 3:
+ OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2]));
+ return;
+ default:
+ throw new ArgumentException();
+ }
+ }
+ };
+ }
+
+ protected abstract void OpenFile(string file);
+ protected abstract void OpenFile(string file, int line);
+ protected abstract void OpenFile(string file, int line, int column);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
new file mode 100644
index 0000000000..e7e81f175e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+
+namespace GodotTools.IdeConnection
+{
+ public abstract class GodotIdeConnection : IDisposable
+ {
+ protected const string Version = "1.0";
+
+ protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}";
+ protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}";
+
+ private const int ClientWriteTimeout = 8000;
+ private readonly TcpClient tcpClient;
+
+ private TextReader clientReader;
+ private TextWriter clientWriter;
+
+ private readonly object writeLock = new object();
+
+ private readonly Func<Message, bool> messageHandler;
+
+ public event Action Connected;
+
+ private ILogger logger;
+
+ public ILogger Logger
+ {
+ get => logger ?? (logger = new ConsoleLogger());
+ set => logger = value;
+ }
+
+ public bool IsDisposed { get; private set; } = false;
+
+ public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected;
+
+ protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler)
+ {
+ this.tcpClient = tcpClient;
+ this.messageHandler = messageHandler;
+ }
+
+ public void Start()
+ {
+ try
+ {
+ if (!StartConnection())
+ return;
+
+ string messageLine;
+ while ((messageLine = ReadLine()) != null)
+ {
+ if (!MessageParser.TryParse(messageLine, out Message msg))
+ {
+ Logger.LogError($"Received message with invalid format: {messageLine}");
+ continue;
+ }
+
+ Logger.LogDebug($"Received message: {msg}");
+
+ if (msg.Id == "close")
+ {
+ Logger.LogInfo("Closing connection");
+ return;
+ }
+
+ try
+ {
+ try
+ {
+ Debug.Assert(messageHandler != null);
+
+ if (!messageHandler(msg))
+ Logger.LogError($"Received unknown message: {msg}");
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Message handler for '{msg}' failed with exception", e);
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Exception thrown from message handler. Message: {msg}", e);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e);
+ }
+ finally
+ {
+ Dispose();
+ }
+ }
+
+ private bool StartConnection()
+ {
+ NetworkStream clientStream = tcpClient.GetStream();
+
+ clientReader = new StreamReader(clientStream, Encoding.UTF8);
+
+ lock (writeLock)
+ clientWriter = new StreamWriter(clientStream, Encoding.UTF8);
+
+ clientStream.WriteTimeout = ClientWriteTimeout;
+
+ if (!WriteHandshake())
+ {
+ Logger.LogError("Could not write handshake");
+ return false;
+ }
+
+ if (!IsValidResponseHandshake(ReadLine()))
+ {
+ Logger.LogError("Received invalid handshake");
+ return false;
+ }
+
+ Connected?.Invoke();
+
+ Logger.LogInfo("Godot Ide connection started");
+
+ return true;
+ }
+
+ private string ReadLine()
+ {
+ try
+ {
+ return clientReader?.ReadLine();
+ }
+ catch (Exception e)
+ {
+ if (IsDisposed)
+ {
+ var se = e as SocketException ?? e.InnerException as SocketException;
+ if (se != null && se.SocketErrorCode == SocketError.Interrupted)
+ return null;
+ }
+
+ throw;
+ }
+ }
+
+ public bool WriteMessage(Message message)
+ {
+ Logger.LogDebug($"Sending message {message}");
+
+ var messageComposer = new MessageComposer();
+
+ messageComposer.AddArgument(message.Id);
+ foreach (string argument in message.Arguments)
+ messageComposer.AddArgument(argument);
+
+ return WriteLine(messageComposer.ToString());
+ }
+
+ protected bool WriteLine(string text)
+ {
+ if (clientWriter == null || IsDisposed || !IsConnected)
+ return false;
+
+ lock (writeLock)
+ {
+ try
+ {
+ clientWriter.WriteLine(text);
+ clientWriter.Flush();
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed)
+ {
+ var se = e as SocketException ?? e.InnerException as SocketException;
+ if (se != null && se.SocketErrorCode == SocketError.Shutdown)
+ Logger.LogInfo("Client disconnected ungracefully");
+ else
+ Logger.LogError("Exception thrown when trying to write to client", e);
+
+ Dispose();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ protected abstract bool WriteHandshake();
+ protected abstract bool IsValidResponseHandshake(string handshakeLine);
+
+ public void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ IsDisposed = true;
+
+ clientReader?.Dispose();
+ clientWriter?.Dispose();
+ ((IDisposable) tcpClient)?.Dispose();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
new file mode 100644
index 0000000000..1b11a14358
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+namespace GodotTools.IdeConnection
+{
+ public class GodotIdeConnectionClient : GodotIdeConnection
+ {
+ public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler)
+ : base(tcpClient, messageHandler)
+ {
+ }
+
+ protected override bool WriteHandshake()
+ {
+ return WriteLine(ClientHandshake);
+ }
+
+ protected override bool IsValidResponseHandshake(string handshakeLine)
+ {
+ return handshakeLine == ServerHandshake;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
new file mode 100644
index 0000000000..aa98dc7ca3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+namespace GodotTools.IdeConnection
+{
+ public class GodotIdeConnectionServer : GodotIdeConnection
+ {
+ public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler)
+ : base(tcpClient, messageHandler)
+ {
+ }
+
+ protected override bool WriteHandshake()
+ {
+ return WriteLine(ServerHandshake);
+ }
+
+ protected override bool IsValidResponseHandshake(string handshakeLine)
+ {
+ return handshakeLine == ClientHandshake;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs
new file mode 100644
index 0000000000..d16daba0e2
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs
@@ -0,0 +1,45 @@
+namespace GodotTools.IdeConnection
+{
+ public struct GodotIdeMetadata
+ {
+ public int Port { get; }
+ public string EditorExecutablePath { get; }
+
+ public GodotIdeMetadata(int port, string editorExecutablePath)
+ {
+ Port = port;
+ EditorExecutablePath = editorExecutablePath;
+ }
+
+ public static bool operator ==(GodotIdeMetadata a, GodotIdeMetadata b)
+ {
+ return a.Port == b.Port && a.EditorExecutablePath == b.EditorExecutablePath;
+ }
+
+ public static bool operator !=(GodotIdeMetadata a, GodotIdeMetadata b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is GodotIdeMetadata metadata)
+ return metadata == this;
+
+ return false;
+ }
+
+ public bool Equals(GodotIdeMetadata other)
+ {
+ return Port == other.Port && EditorExecutablePath == other.EditorExecutablePath;
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Port * 397) ^ (EditorExecutablePath != null ? EditorExecutablePath.GetHashCode() : 0);
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
new file mode 100644
index 0000000000..94e525715b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>GodotTools.IdeConnection</RootNamespace>
+ <AssemblyName>GodotTools.IdeConnection</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <LangVersion>7</LangVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>portable</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>portable</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ConsoleLogger.cs" />
+ <Compile Include="GodotIdeMetadata.cs" />
+ <Compile Include="GodotIdeBase.cs" />
+ <Compile Include="GodotIdeClient.cs" />
+ <Compile Include="GodotIdeConnection.cs" />
+ <Compile Include="GodotIdeConnectionClient.cs" />
+ <Compile Include="GodotIdeConnectionServer.cs" />
+ <Compile Include="ILogger.cs" />
+ <Compile Include="Message.cs" />
+ <Compile Include="MessageComposer.cs" />
+ <Compile Include="MessageParser.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs
new file mode 100644
index 0000000000..614bb30271
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace GodotTools.IdeConnection
+{
+ public interface ILogger
+ {
+ void LogDebug(string message);
+ void LogInfo(string message);
+ void LogWarning(string message);
+ void LogError(string message);
+ void LogError(string message, Exception e);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
new file mode 100644
index 0000000000..f24d324ae3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
@@ -0,0 +1,21 @@
+using System.Linq;
+
+namespace GodotTools.IdeConnection
+{
+ public struct Message
+ {
+ public string Id { get; set; }
+ public string[] Arguments { get; set; }
+
+ public Message(string id, params string[] arguments)
+ {
+ Id = id;
+ Arguments = arguments;
+ }
+
+ public override string ToString()
+ {
+ return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')";
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
new file mode 100644
index 0000000000..9e4cd6ec1a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
@@ -0,0 +1,46 @@
+using System.Linq;
+using System.Text;
+
+namespace GodotTools.IdeConnection
+{
+ public class MessageComposer
+ {
+ private readonly StringBuilder stringBuilder = new StringBuilder();
+
+ private static readonly char[] CharsToEscape = { '\\', '"' };
+
+ public void AddArgument(string argument)
+ {
+ AddArgument(argument, quoted: argument.Contains(","));
+ }
+
+ public void AddArgument(string argument, bool quoted)
+ {
+ if (stringBuilder.Length > 0)
+ stringBuilder.Append(',');
+
+ if (quoted)
+ {
+ stringBuilder.Append('"');
+
+ foreach (char @char in argument)
+ {
+ if (CharsToEscape.Contains(@char))
+ stringBuilder.Append('\\');
+ stringBuilder.Append(@char);
+ }
+
+ stringBuilder.Append('"');
+ }
+ else
+ {
+ stringBuilder.Append(argument);
+ }
+ }
+
+ public override string ToString()
+ {
+ return stringBuilder.ToString();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
new file mode 100644
index 0000000000..ed691e481f
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
@@ -0,0 +1,88 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace GodotTools.IdeConnection
+{
+ public static class MessageParser
+ {
+ public static bool TryParse(string messageLine, out Message message)
+ {
+ var arguments = new List<string>();
+ var stringBuilder = new StringBuilder();
+
+ bool expectingArgument = true;
+
+ for (int i = 0; i < messageLine.Length; i++)
+ {
+ char @char = messageLine[i];
+
+ if (@char == ',')
+ {
+ if (expectingArgument)
+ arguments.Add(string.Empty);
+
+ expectingArgument = true;
+ continue;
+ }
+
+ bool quoted = false;
+
+ if (messageLine[i] == '"')
+ {
+ quoted = true;
+ i++;
+ }
+
+ while (i < messageLine.Length)
+ {
+ @char = messageLine[i];
+
+ if (quoted && @char == '"')
+ {
+ i++;
+ break;
+ }
+
+ if (@char == '\\')
+ {
+ i++;
+ if (i < messageLine.Length)
+ break;
+
+ stringBuilder.Append(messageLine[i]);
+ }
+ else if (!quoted && @char == ',')
+ {
+ break; // We don't increment the counter to allow the colon to be parsed after this
+ }
+ else
+ {
+ stringBuilder.Append(@char);
+ }
+
+ i++;
+ }
+
+ arguments.Add(stringBuilder.ToString());
+ stringBuilder.Clear();
+
+ expectingArgument = false;
+ }
+
+ if (arguments.Count == 0)
+ {
+ message = new Message();
+ return false;
+ }
+
+ message = new Message
+ {
+ Id = arguments[0],
+ Arguments = arguments.Skip(1).ToArray()
+ };
+
+ return true;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c7c00e66a2
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/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.IdeConnection")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("Godot Engine contributors")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.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/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
new file mode 100644
index 0000000000..ab3a5d1aea
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
@@ -0,0 +1,64 @@
+<?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>GodotTools.ProjectEditor</RootNamespace>
+ <AssemblyName>GodotTools.ProjectEditor</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+ <LangVersion>7</LangVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>portable</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="Microsoft.Build" />
+ <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL">
+ <!--
+ 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)' != '' And Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
+ <HintPath Condition=" '$(SolutionDir)' == '' Or !Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ApiAssembliesInfo.cs" />
+ <Compile Include="ApiSolutionGenerator.cs" />
+ <Compile Include="DotNetSolution.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="IdentifierUtils.cs" />
+ <Compile Include="ProjectExtensions.cs" />
+ <Compile Include="ProjectGenerator.cs" />
+ <Compile Include="ProjectUtils.cs" />
+ </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>
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
new file mode 100644
index 0000000000..f93eb9a1fa
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace GodotTools.ProjectEditor
+{
+ public static class IdentifierUtils
+ {
+ public static string SanitizeQualifiedIdentifier(string qualifiedIdentifier, bool allowEmptyIdentifiers)
+ {
+ if (string.IsNullOrEmpty(qualifiedIdentifier))
+ throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier));
+
+ string[] identifiers = qualifiedIdentifier.Split('.');
+
+ for (int i = 0; i < identifiers.Length; i++)
+ {
+ identifiers[i] = SanitizeIdentifier(identifiers[i], allowEmpty: allowEmptyIdentifiers);
+ }
+
+ return string.Join(".", identifiers);
+ }
+
+ public static string SanitizeIdentifier(string identifier, bool allowEmpty)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ if (allowEmpty)
+ return "Empty"; // Default value for empty identifiers
+
+ throw new ArgumentException($"{nameof(identifier)} cannot be empty if {nameof(allowEmpty)} is false", nameof(identifier));
+ }
+
+ if (identifier.Length > 511)
+ identifier = identifier.Substring(0, 511);
+
+ var identifierBuilder = new StringBuilder();
+ int startIndex = 0;
+
+ if (identifier[0] == '@')
+ {
+ identifierBuilder.Append('@');
+ startIndex += 1;
+ }
+
+ for (int i = startIndex; i < identifier.Length; i++)
+ {
+ char @char = identifier[i];
+
+ switch (Char.GetUnicodeCategory(@char))
+ {
+ case UnicodeCategory.UppercaseLetter:
+ case UnicodeCategory.LowercaseLetter:
+ case UnicodeCategory.TitlecaseLetter:
+ case UnicodeCategory.ModifierLetter:
+ case UnicodeCategory.LetterNumber:
+ case UnicodeCategory.OtherLetter:
+ identifierBuilder.Append(@char);
+ break;
+ case UnicodeCategory.NonSpacingMark:
+ case UnicodeCategory.SpacingCombiningMark:
+ case UnicodeCategory.ConnectorPunctuation:
+ case UnicodeCategory.DecimalDigitNumber:
+ // Identifiers may start with underscore
+ if (identifierBuilder.Length > startIndex || @char == '_')
+ identifierBuilder.Append(@char);
+ break;
+ }
+ }
+
+ if (identifierBuilder.Length == startIndex)
+ {
+ // All characters were invalid so now it's empty. Fill it with something.
+ identifierBuilder.Append("Empty");
+ }
+
+ identifier = identifierBuilder.ToString();
+
+ if (identifier[0] != '@' && IsKeyword(identifier, anyDoubleUnderscore: true))
+ identifier = '@' + identifier;
+
+ return identifier;
+ }
+
+ static bool IsKeyword(string value, bool anyDoubleUnderscore)
+ {
+ // Identifiers that start with double underscore are meant to be used for reserved keywords.
+ // Only existing keywords are enforced, but it may be useful to forbid any identifier
+ // that begins with double underscore to prevent issues with future C# versions.
+ if (anyDoubleUnderscore)
+ {
+ if (value.Length > 2 && value[0] == '_' && value[1] == '_' && value[2] != '_')
+ return true;
+ }
+ else
+ {
+ if (DoubleUnderscoreKeywords.Contains(value))
+ return true;
+ }
+
+ return Keywords.Contains(value);
+ }
+
+ private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string>
+ {
+ "__arglist",
+ "__makeref",
+ "__reftype",
+ "__refvalue",
+ };
+
+ private static readonly HashSet<string> Keywords = new HashSet<string>
+ {
+ "as",
+ "do",
+ "if",
+ "in",
+ "is",
+ "for",
+ "int",
+ "new",
+ "out",
+ "ref",
+ "try",
+ "base",
+ "bool",
+ "byte",
+ "case",
+ "char",
+ "else",
+ "enum",
+ "goto",
+ "lock",
+ "long",
+ "null",
+ "this",
+ "true",
+ "uint",
+ "void",
+ "break",
+ "catch",
+ "class",
+ "const",
+ "event",
+ "false",
+ "fixed",
+ "float",
+ "sbyte",
+ "short",
+ "throw",
+ "ulong",
+ "using",
+ "where",
+ "while",
+ "yield",
+ "double",
+ "extern",
+ "object",
+ "params",
+ "public",
+ "return",
+ "sealed",
+ "sizeof",
+ "static",
+ "string",
+ "struct",
+ "switch",
+ "typeof",
+ "unsafe",
+ "ushort",
+ "checked",
+ "decimal",
+ "default",
+ "finally",
+ "foreach",
+ "partial",
+ "private",
+ "virtual",
+ "abstract",
+ "continue",
+ "delegate",
+ "explicit",
+ "implicit",
+ "internal",
+ "operator",
+ "override",
+ "readonly",
+ "volatile",
+ "interface",
+ "namespace",
+ "protected",
+ "unchecked",
+ "stackalloc",
+ };
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
index f00ec5a2ad..36961eb45e 100644
--- a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
@@ -1,13 +1,18 @@
+using GodotTools.Core;
using System;
+using DotNet.Globbing;
using Microsoft.Build.Construction;
-namespace GodotSharpTools.Project
+namespace GodotTools.ProjectEditor
{
public static class ProjectExtensions
{
public static bool HasItem(this ProjectRootElement root, string itemType, string include)
{
- string includeNormalized = include.NormalizePath();
+ GlobOptions globOptions = new GlobOptions();
+ globOptions.Evaluation.CaseInsensitive = false;
+
+ string normalizedInclude = include.NormalizePath();
foreach (var itemGroup in root.ItemGroups)
{
@@ -16,10 +21,14 @@ namespace GodotSharpTools.Project
foreach (var item in itemGroup.Items)
{
- if (item.ItemType == itemType)
+ if (item.ItemType != itemType)
+ continue;
+
+ var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
+
+ if (glob.IsMatch(normalizedInclude))
{
- if (item.Include.NormalizePath() == includeNormalized)
- return true;
+ return true;
}
}
}
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index 1d863e6f61..4f21871f1a 100644
--- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,24 +1,33 @@
+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 static string GenCoreApiProject(string dir, string[] compileItems)
+ 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, IEnumerable<string> compileItems)
{
- string path = Path.Combine(dir, CoreApiProject + ".csproj");
+ string path = Path.Combine(dir, CoreApiProjectName + ".csproj");
ProjectPropertyGroupElement mainGroup;
- var root = CreateLibraryProject(CoreApiProject, out mainGroup);
+ var root = CreateLibraryProject(CoreApiProjectName, out mainGroup);
mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml"));
mainGroup.SetProperty("RootNamespace", "Godot");
+ mainGroup.SetProperty("ProjectGuid", CoreApiProjectGuid);
+ mainGroup.SetProperty("BaseIntermediateOutputPath", "obj");
- GenAssemblyInfoFile(root, dir, CoreApiProject,
- new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProject + "\")]" },
- new string[] { "System.Runtime.CompilerServices" });
+ GenAssemblyInfoFile(root, dir, CoreApiProjectName,
+ new[] {"[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]"},
+ new[] {"System.Runtime.CompilerServices"});
foreach (var item in compileItems)
{
@@ -27,36 +36,37 @@ namespace GodotSharpTools.Project
root.Save(path);
- return root.GetGuid().ToString().ToUpper();
+ return CoreApiProjectGuid;
}
- public static string GenEditorApiProject(string dir, string coreApiHintPath, string[] compileItems)
+ public static string GenEditorApiProject(string dir, string coreApiProjPath, IEnumerable<string> compileItems)
{
- string path = Path.Combine(dir, EditorApiProject + ".csproj");
+ string path = Path.Combine(dir, EditorApiProjectName + ".csproj");
ProjectPropertyGroupElement mainGroup;
- var root = CreateLibraryProject(EditorApiProject, out mainGroup);
+ var root = CreateLibraryProject(EditorApiProjectName, out mainGroup);
mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml"));
mainGroup.SetProperty("RootNamespace", "Godot");
+ mainGroup.SetProperty("ProjectGuid", EditorApiProjectGuid);
+ mainGroup.SetProperty("BaseIntermediateOutputPath", "obj");
- GenAssemblyInfoFile(root, dir, EditorApiProject);
+ GenAssemblyInfoFile(root, dir, EditorApiProjectName);
foreach (var item in compileItems)
{
root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
}
- var coreApiRef = root.AddItem("Reference", CoreApiProject);
- coreApiRef.AddMetadata("HintPath", coreApiHintPath);
+ var coreApiRef = root.AddItem("ProjectReference", coreApiProjPath.Replace("/", "\\"));
coreApiRef.AddMetadata("Private", "False");
root.Save(path);
- return root.GetGuid().ToString().ToUpper();
+ 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");
@@ -66,24 +76,26 @@ 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", CoreApiProject);
- coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProject + ".dll"));
+ var coreApiRef = root.AddItem("Reference", CoreApiProjectName);
+ coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
coreApiRef.AddMetadata("Private", "False");
- var editorApiRef = root.AddItem("Reference", EditorApiProject);
+ var editorApiRef = root.AddItem("Reference", EditorApiProjectName);
editorApiRef.Condition = " '$(Configuration)' == 'Tools' ";
- editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProject + ".dll"));
+ editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
editorApiRef.AddMetadata("Private", "False");
GenAssemblyInfoFile(root, dir, name);
@@ -100,7 +112,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);
@@ -116,12 +127,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");
@@ -132,6 +140,9 @@ namespace GodotSharpTools.Project
public static ProjectRootElement CreateLibraryProject(string name, out ProjectPropertyGroupElement mainGroup)
{
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException($"{nameof(name)} cannot be empty", nameof(name));
+
var root = ProjectRootElement.Create();
root.DefaultTargets = "Build";
@@ -141,7 +152,7 @@ namespace GodotSharpTools.Project
mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
mainGroup.AddProperty("OutputType", "Library");
mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)"));
- mainGroup.AddProperty("RootNamespace", name);
+ mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true));
mainGroup.AddProperty("AssemblyName", name);
mainGroup.AddProperty("TargetFrameworkVersion", "v4.5");
@@ -150,7 +161,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");
@@ -159,6 +170,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");
@@ -182,11 +194,8 @@ namespace GodotSharpTools.Project
}
}
- public const string CoreApiProject = "GodotSharp";
- public const string EditorApiProject = "GodotSharpEditor";
-
- 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/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
new file mode 100644
index 0000000000..233aab45b3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -0,0 +1,175 @@
+using GodotTools.Core;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using DotNet.Globbing;
+using Microsoft.Build.Construction;
+
+namespace GodotTools.ProjectEditor
+{
+ public static class ProjectUtils
+ {
+ public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
+ {
+ var dir = Directory.GetParent(projectPath).FullName;
+ var root = ProjectRootElement.Open(projectPath);
+ Debug.Assert(root != null);
+
+ var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
+
+ if (root.AddItemChecked(itemType, normalizedInclude))
+ root.Save();
+ }
+
+ private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
+ {
+ string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
+
+ // We want relative paths
+ for (int i = 0; i < files.Length; i++)
+ {
+ files[i] = files[i].RelativeToPath(rootDirectory);
+ }
+
+ return files;
+ }
+
+ public static string[] GetIncludeFiles(string projectPath, string itemType)
+ {
+ var result = new List<string>();
+ var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
+
+ var globOptions = new GlobOptions();
+ globOptions.Evaluation.CaseInsensitive = false;
+
+ var root = ProjectRootElement.Open(projectPath);
+
+ foreach (var itemGroup in root.ItemGroups)
+ {
+ if (itemGroup.Condition.Length != 0)
+ continue;
+
+ foreach (var item in itemGroup.Items)
+ {
+ if (item.ItemType != itemType)
+ continue;
+
+ string normalizedInclude = item.Include.NormalizePath();
+
+ var glob = Glob.Parse(normalizedInclude, globOptions);
+
+ // TODO Check somehow if path has no blob to avoid the following loop...
+
+ foreach (var existingFile in existingFiles)
+ {
+ if (glob.IsMatch(existingFile))
+ {
+ result.Add(existingFile);
+ }
+ }
+ }
+ }
+
+ return result.ToArray();
+ }
+
+ /// Simple function to make sure the Api assembly references are configured correctly
+ public static void FixApiHintPath(string projectPath)
+ {
+ var root = ProjectRootElement.Open(projectPath);
+ Debug.Assert(root != null);
+
+ bool dirty = false;
+
+ void AddPropertyIfNotPresent(string name, string condition, string value)
+ {
+ if (root.PropertyGroups
+ .Any(g => (g.Condition == string.Empty || g.Condition == condition) &&
+ g.Properties
+ .Any(p => p.Name == name &&
+ p.Value == value &&
+ (p.Condition == condition || g.Condition == condition))))
+ {
+ return;
+ }
+
+ root.AddProperty(name, value).Condition = condition;
+ dirty = true;
+ }
+
+ AddPropertyIfNotPresent(name: "ApiConfiguration",
+ condition: " '$(Configuration)' != 'Release' ",
+ value: "Debug");
+ AddPropertyIfNotPresent(name: "ApiConfiguration",
+ condition: " '$(Configuration)' == 'Release' ",
+ value: "Release");
+
+ void SetReferenceHintPath(string referenceName, string condition, string hintPath)
+ {
+ foreach (var itemGroup in root.ItemGroups.Where(g =>
+ g.Condition == string.Empty || g.Condition == condition))
+ {
+ var references = itemGroup.Items.Where(item =>
+ item.ItemType == "Reference" &&
+ item.Include == referenceName &&
+ (item.Condition == condition || itemGroup.Condition == condition));
+
+ var referencesWithHintPath = references.Where(reference =>
+ reference.Metadata.Any(m => m.Name == "HintPath"));
+
+ if (referencesWithHintPath.Any(reference => reference.Metadata
+ .Any(m => m.Name == "HintPath" && m.Value == hintPath)))
+ {
+ // Found a Reference item with the right HintPath
+ return;
+ }
+
+ var referenceWithHintPath = referencesWithHintPath.FirstOrDefault();
+ if (referenceWithHintPath != null)
+ {
+ // Found a Reference item with a wrong HintPath
+ foreach (var metadata in referenceWithHintPath.Metadata.ToList()
+ .Where(m => m.Name == "HintPath"))
+ {
+ // Safe to remove as we duplicate with ToList() to loop
+ referenceWithHintPath.RemoveChild(metadata);
+ }
+
+ referenceWithHintPath.AddMetadata("HintPath", hintPath);
+ dirty = true;
+ return;
+ }
+
+ var referenceWithoutHintPath = references.FirstOrDefault();
+ if (referenceWithoutHintPath != null)
+ {
+ // Found a Reference item without a HintPath
+ referenceWithoutHintPath.AddMetadata("HintPath", hintPath);
+ dirty = true;
+ return;
+ }
+ }
+
+ // Found no Reference item at all. Add it.
+ root.AddItem("Reference", referenceName).Condition = condition;
+ dirty = true;
+ }
+
+ const string coreProjectName = "GodotSharp";
+ const string editorProjectName = "GodotSharpEditor";
+
+ const string coreCondition = "";
+ const string editorCondition = " '$(Configuration)' == 'Tools' ";
+
+ var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll";
+ var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll";
+
+ SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath);
+ SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath);
+
+ if (dirty)
+ root.Save();
+ }
+ }
+}
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/GodotTools/GodotTools.ProjectEditor/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
new file mode 100644
index 0000000000..13915000e4
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
@@ -0,0 +1,4 @@
+<?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.sln b/modules/mono/editor/GodotTools/GodotTools.sln
new file mode 100644
index 0000000000..a3438ea5f3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.sln
@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.ProjectEditor", "GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools", "GodotTools\GodotTools.csproj", "{27B00618-A6F2-4828-B922-05CAEB08C286}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotTools.Core\GodotTools.Core.csproj", "{639E48BD-44E5-4091-8EDD-22D36DC0768D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}"
+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
+ {27B00618-A6F2-4828-B922-05CAEB08C286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {27B00618-A6F2-4828-B922-05CAEB08C286}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27B00618-A6F2-4828-B922-05CAEB08C286}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {27B00618-A6F2-4828-B922-05CAEB08C286}.Release|Any CPU.Build.0 = Release|Any CPU
+ {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
new file mode 100644
index 0000000000..44813f962c
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
@@ -0,0 +1,343 @@
+using Godot;
+using System;
+using System.IO;
+using Godot.Collections;
+using GodotTools.Internals;
+using static GodotTools.Internals.Globals;
+using File = GodotTools.Utils.File;
+using Path = System.IO.Path;
+
+namespace GodotTools
+{
+ public class BottomPanel : VBoxContainer
+ {
+ private EditorInterface editorInterface;
+
+ private TabContainer panelTabs;
+
+ private VBoxContainer panelBuildsTab;
+
+ private ItemList buildTabsList;
+ private TabContainer buildTabs;
+
+ private ToolButton warningsBtn;
+ private ToolButton errorsBtn;
+ private Button viewLogBtn;
+
+ private void _UpdateBuildTabsList()
+ {
+ buildTabsList.Clear();
+
+ int currentTab = buildTabs.CurrentTab;
+
+ bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
+
+ for (int i = 0; i < buildTabs.GetChildCount(); i++)
+ {
+ var tab = (BuildTab) buildTabs.GetChild(i);
+
+ if (tab == null)
+ continue;
+
+ string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
+ itemName += " [" + tab.BuildInfo.Configuration + "]";
+
+ buildTabsList.AddItem(itemName, tab.IconTexture);
+
+ string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
+ itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
+ itemTooltip += "\nStatus: ";
+
+ if (tab.BuildExited)
+ itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
+ else
+ itemTooltip += "Running";
+
+ if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
+ itemTooltip += $"\nErrors: {tab.ErrorCount}";
+
+ itemTooltip += $"\nWarnings: {tab.WarningCount}";
+
+ buildTabsList.SetItemTooltip(i, itemTooltip);
+
+ if (noCurrentTab || currentTab == i)
+ {
+ buildTabsList.Select(i);
+ _BuildTabsItemSelected(i);
+ }
+ }
+ }
+
+ public BuildTab GetBuildTabFor(BuildInfo buildInfo)
+ {
+ foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren()))
+ {
+ if (buildTab.BuildInfo.Equals(buildInfo))
+ return buildTab;
+ }
+
+ var newBuildTab = new BuildTab(buildInfo);
+ AddBuildTab(newBuildTab);
+
+ return newBuildTab;
+ }
+
+ private void _BuildTabsItemSelected(int idx)
+ {
+ if (idx < 0 || idx >= buildTabs.GetTabCount())
+ throw new IndexOutOfRangeException();
+
+ buildTabs.CurrentTab = idx;
+ if (!buildTabs.Visible)
+ buildTabs.Visible = true;
+
+ warningsBtn.Visible = true;
+ errorsBtn.Visible = true;
+ viewLogBtn.Visible = true;
+ }
+
+ private void _BuildTabsNothingSelected()
+ {
+ if (buildTabs.GetTabCount() != 0)
+ {
+ // just in case
+ buildTabs.Visible = false;
+
+ // This callback is called when clicking on the empty space of the list.
+ // ItemList won't deselect the items automatically, so we must do it ourselves.
+ buildTabsList.UnselectAll();
+ }
+
+ warningsBtn.Visible = false;
+ errorsBtn.Visible = false;
+ viewLogBtn.Visible = false;
+ }
+
+ private void _WarningsToggled(bool pressed)
+ {
+ int currentTab = buildTabs.CurrentTab;
+
+ if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+ throw new InvalidOperationException("No tab selected");
+
+ var buildTab = (BuildTab) buildTabs.GetChild(currentTab);
+ buildTab.WarningsVisible = pressed;
+ buildTab.UpdateIssuesList();
+ }
+
+ private void _ErrorsToggled(bool pressed)
+ {
+ int currentTab = buildTabs.CurrentTab;
+
+ if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+ throw new InvalidOperationException("No tab selected");
+
+ var buildTab = (BuildTab) buildTabs.GetChild(currentTab);
+ buildTab.ErrorsVisible = pressed;
+ buildTab.UpdateIssuesList();
+ }
+
+ public void BuildProjectPressed()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return; // No solution to build
+
+ string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
+ string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
+
+ CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
+
+ if (File.Exists(editorScriptsMetadataPath))
+ {
+ try
+ {
+ File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
+ }
+ catch (IOException e)
+ {
+ GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}");
+ return;
+ }
+ }
+
+ var godotDefines = new[]
+ {
+ OS.GetName(),
+ Internal.GodotIs32Bits() ? "32" : "64"
+ };
+
+ bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines);
+
+ if (!buildSuccess)
+ return;
+
+ // Notify running game for hot-reload
+ Internal.ScriptEditorDebuggerReloadScripts();
+
+ // Hot-reload in the editor
+ GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
+
+ if (Internal.IsAssembliesReloadingNeeded())
+ Internal.ReloadAssemblies(softReload: false);
+ }
+
+ private void _ViewLogPressed()
+ {
+ if (!buildTabsList.IsAnythingSelected())
+ return;
+
+ var selectedItems = buildTabsList.GetSelectedItems();
+
+ if (selectedItems.Length != 1)
+ throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}");
+
+ int selectedItem = selectedItems[0];
+
+ var buildTab = (BuildTab) buildTabs.GetTabControl(selectedItem);
+
+ OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName));
+ }
+
+ public override void _Notification(int what)
+ {
+ base._Notification(what);
+
+ if (what == EditorSettings.NotificationEditorSettingsChanged)
+ {
+ var editorBaseControl = editorInterface.GetBaseControl();
+ panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
+ }
+ }
+
+ public void AddBuildTab(BuildTab buildTab)
+ {
+ buildTabs.AddChild(buildTab);
+ RaiseBuildTab(buildTab);
+ }
+
+ public void RaiseBuildTab(BuildTab buildTab)
+ {
+ if (buildTab.GetParent() != buildTabs)
+ throw new InvalidOperationException("Build tab is not in the tabs list");
+
+ buildTabs.MoveChild(buildTab, 0);
+ _UpdateBuildTabsList();
+ }
+
+ public void ShowBuildTab()
+ {
+ for (int i = 0; i < panelTabs.GetTabCount(); i++)
+ {
+ if (panelTabs.GetTabControl(i) == panelBuildsTab)
+ {
+ panelTabs.CurrentTab = i;
+ GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this);
+ return;
+ }
+ }
+
+ GD.PushError("Builds tab not found");
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ editorInterface = GodotSharpEditor.Instance.GetEditorInterface();
+
+ var editorBaseControl = editorInterface.GetBaseControl();
+
+ SizeFlagsVertical = (int) SizeFlags.ExpandFill;
+ SetAnchorsAndMarginsPreset(LayoutPreset.Wide);
+
+ panelTabs = new TabContainer
+ {
+ TabAlign = TabContainer.TabAlignEnum.Left,
+ RectMinSize = new Vector2(0, 228) * EditorScale,
+ SizeFlagsVertical = (int) SizeFlags.ExpandFill
+ };
+ panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
+ panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
+ AddChild(panelTabs);
+
+ {
+ // Builds tab
+ panelBuildsTab = new VBoxContainer
+ {
+ Name = "Builds".TTR(),
+ SizeFlagsHorizontal = (int) SizeFlags.ExpandFill
+ };
+ panelTabs.AddChild(panelBuildsTab);
+
+ var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
+ panelBuildsTab.AddChild(toolBarHBox);
+
+ var buildProjectBtn = new Button
+ {
+ Text = "Build Project".TTR(),
+ FocusMode = FocusModeEnum.None
+ };
+ buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed));
+ toolBarHBox.AddChild(buildProjectBtn);
+
+ toolBarHBox.AddSpacer(begin: false);
+
+ warningsBtn = new ToolButton
+ {
+ Text = "Warnings".TTR(),
+ ToggleMode = true,
+ Pressed = true,
+ Visible = false,
+ FocusMode = FocusModeEnum.None
+ };
+ warningsBtn.Connect("toggled", this, nameof(_WarningsToggled));
+ toolBarHBox.AddChild(warningsBtn);
+
+ errorsBtn = new ToolButton
+ {
+ Text = "Errors".TTR(),
+ ToggleMode = true,
+ Pressed = true,
+ Visible = false,
+ FocusMode = FocusModeEnum.None
+ };
+ errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled));
+ toolBarHBox.AddChild(errorsBtn);
+
+ toolBarHBox.AddSpacer(begin: false);
+
+ viewLogBtn = new Button
+ {
+ Text = "View log".TTR(),
+ FocusMode = FocusModeEnum.None,
+ Visible = false
+ };
+ viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed));
+ toolBarHBox.AddChild(viewLogBtn);
+
+ var hsc = new HSplitContainer
+ {
+ SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
+ SizeFlagsVertical = (int) SizeFlags.ExpandFill
+ };
+ panelBuildsTab.AddChild(hsc);
+
+ buildTabsList = new ItemList {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
+ buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected));
+ buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected));
+ hsc.AddChild(buildTabsList);
+
+ buildTabs = new TabContainer
+ {
+ TabAlign = TabContainer.TabAlignEnum.Left,
+ SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
+ TabsVisible = false
+ };
+ hsc.AddChild(buildTabs);
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
new file mode 100644
index 0000000000..9a2b2e3a26
--- /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 (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
+ == BuildManager.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(BuildInfo buildInfo)
+ {
+ return Build(buildInfo.Solution, buildInfo.Configuration,
+ buildInfo.LogsDirPath, buildInfo.CustomProperties);
+ }
+
+ public static async Task<int> BuildAsync(BuildInfo buildInfo)
+ {
+ return await BuildAsync(buildInfo.Solution, buildInfo.Configuration,
+ buildInfo.LogsDirPath, buildInfo.CustomProperties);
+ }
+
+ public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ {
+ using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ {
+ 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:Build ""/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..eb2c2dd77c
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -0,0 +1,186 @@
+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;
+
+ public static string FindMsBuild()
+ {
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
+
+ if (OS.IsWindows())
+ {
+ switch (buildTool)
+ {
+ case BuildManager.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 '{BuildManager.PropNameMsbuildVs}'.");
+ }
+ }
+
+ if (!_msbuildToolsPath.EndsWith("\\"))
+ _msbuildToolsPath += "\\";
+
+ return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
+ }
+ case BuildManager.BuildTool.MsBuildMono:
+ {
+ string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
+
+ if (!File.Exists(msbuildPath))
+ {
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
+ }
+
+ return msbuildPath;
+ }
+ default:
+ throw new IndexOutOfRangeException("Invalid build tool in editor settings");
+ }
+ }
+
+ if (OS.IsUnix())
+ {
+ if (buildTool == BuildManager.BuildTool.MsBuildMono)
+ {
+ 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 '{BuildManager.PropNameMsbuildMono}'");
+ }
+
+ return _msbuildUnixPath;
+ }
+ else
+ {
+ throw new IndexOutOfRangeException("Invalid build tool in editor settings");
+ }
+ }
+
+ 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[0].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/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
new file mode 100644
index 0000000000..70bd552f2f
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.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 BuildInfo : 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 BuildInfo 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 BuildInfo()
+ {
+ }
+
+ public BuildInfo(string solution, string configuration)
+ {
+ Solution = solution;
+ Configuration = configuration;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
new file mode 100644
index 0000000000..417032da54
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -0,0 +1,259 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using GodotTools.Build;
+using GodotTools.Internals;
+using GodotTools.Utils;
+using static GodotTools.Internals.Globals;
+using File = GodotTools.Utils.File;
+
+namespace GodotTools
+{
+ public static class BuildManager
+ {
+ private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>();
+
+ public const string PropNameMsbuildMono = "MSBuild (Mono)";
+ public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
+
+ public const string MsBuildIssuesFileName = "msbuild_issues.csv";
+ public const string MsBuildLogFileName = "msbuild_log.txt";
+
+ public enum BuildTool
+ {
+ MsBuildMono,
+ MsBuildVs
+ }
+
+ private static void RemoveOldIssuesFile(BuildInfo buildInfo)
+ {
+ var issuesFile = GetIssuesFilePath(buildInfo);
+
+ if (!File.Exists(issuesFile))
+ return;
+
+ File.Delete(issuesFile);
+ }
+
+ private static void ShowBuildErrorDialog(string message)
+ {
+ GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
+ GodotSharpEditor.Instance.BottomPanel.ShowBuildTab();
+ }
+
+ public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException();
+ public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException();
+
+ private static string GetLogFilePath(BuildInfo buildInfo)
+ {
+ return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
+ }
+
+ private static string GetIssuesFilePath(BuildInfo buildInfo)
+ {
+ return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName);
+ }
+
+ private static void PrintVerbose(string text)
+ {
+ if (Godot.OS.IsStdoutVerbose())
+ Godot.GD.Print(text);
+ }
+
+ public static bool Build(BuildInfo buildInfo)
+ {
+ if (BuildsInProgress.Contains(buildInfo))
+ throw new InvalidOperationException("A build is already in progress");
+
+ BuildsInProgress.Add(buildInfo);
+
+ try
+ {
+ BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
+ buildTab.OnBuildStart();
+
+ // Required in order to update the build tasks list
+ Internal.GodotMainIteration();
+
+ try
+ {
+ RemoveOldIssuesFile(buildInfo);
+ }
+ catch (IOException e)
+ {
+ buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
+ Console.Error.WriteLine(e);
+ }
+
+ try
+ {
+ int exitCode = BuildSystem.Build(buildInfo);
+
+ if (exitCode != 0)
+ PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
+
+ buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+ finally
+ {
+ BuildsInProgress.Remove(buildInfo);
+ }
+ }
+
+ public static async Task<bool> BuildAsync(BuildInfo buildInfo)
+ {
+ if (BuildsInProgress.Contains(buildInfo))
+ throw new InvalidOperationException("A build is already in progress");
+
+ BuildsInProgress.Add(buildInfo);
+
+ try
+ {
+ BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
+
+ try
+ {
+ RemoveOldIssuesFile(buildInfo);
+ }
+ catch (IOException e)
+ {
+ buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
+ Console.Error.WriteLine(e);
+ }
+
+ try
+ {
+ int exitCode = await BuildSystem.BuildAsync(buildInfo);
+
+ if (exitCode != 0)
+ PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
+
+ buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+ finally
+ {
+ BuildsInProgress.Remove(buildInfo);
+ }
+ }
+
+ public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return true; // No solution to build
+
+ // Make sure to update the API assemblies if they happen to be missing. Just in
+ // case the user decided to delete them at some point after they were loaded.
+ Internal.UpdateApiAssembliesFromPrebuilt();
+
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
+
+ using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
+ {
+ pr.Step("Building project solution", 0);
+
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config);
+
+ // Add Godot defines
+ string constants = buildTool == BuildTool.MsBuildVs ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
+
+ foreach (var godotDefine in godotDefines)
+ constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
+
+ if (Internal.GodotIsRealTDouble())
+ constants += "GODOT_REAL_T_IS_DOUBLE;";
+
+ constants += buildTool == BuildTool.MsBuildVs ? "\"" : "\\\"";
+
+ buildInfo.CustomProperties.Add(constants);
+
+ if (!Build(buildInfo))
+ {
+ ShowBuildErrorDialog("Failed to build project solution");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool EditorBuildCallback()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return true; // No solution to build
+
+ string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
+ string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
+
+ CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
+
+ if (File.Exists(editorScriptsMetadataPath))
+ File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
+
+ var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest;
+
+ if (currentPlayRequest != null)
+ {
+ if (currentPlayRequest.Value.HasDebugger)
+ {
+ // Set the environment variable that will tell the player to connect to the IDE debugger
+ // TODO: We should probably add a better way to do this
+ Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT",
+ "--debugger-agent=transport=dt_socket" +
+ $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" +
+ ",server=n");
+ }
+
+ return true; // Requested play from an external editor/IDE which already built the project
+ }
+
+ var godotDefines = new[]
+ {
+ Godot.OS.GetName(),
+ Internal.GodotIs32Bits() ? "32" : "64"
+ };
+
+ return BuildProjectBlocking("Tools", godotDefines);
+ }
+
+ public static void Initialize()
+ {
+ // Build tool settings
+
+ EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono);
+
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+
+ editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = Godot.Variant.Type.Int,
+ ["name"] = "mono/builds/build_tool",
+ ["hint"] = Godot.PropertyHint.Enum,
+ ["hint_string"] = OS.IsWindows() ?
+ $"{PropNameMsbuildMono},{PropNameMsbuildVs}" :
+ $"{PropNameMsbuildMono}"
+ });
+
+ EditorDef("mono/builds/print_build_output", false);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
new file mode 100644
index 0000000000..807a20d9a1
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
@@ -0,0 +1,267 @@
+using Godot;
+using System;
+using Godot.Collections;
+using GodotTools.Internals;
+using File = GodotTools.Utils.File;
+using Path = System.IO.Path;
+
+namespace GodotTools
+{
+ public class BuildTab : VBoxContainer
+ {
+ public enum BuildResults
+ {
+ Error,
+ Success
+ }
+
+ [Serializable]
+ private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization
+ {
+ public bool Warning { get; set; }
+ public string File { get; set; }
+ public int Line { get; set; }
+ public int Column { get; set; }
+ public string Code { get; set; }
+ public string Message { get; set; }
+ public string ProjectFile { get; set; }
+ }
+
+ private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
+ private ItemList issuesList;
+
+ public bool BuildExited { get; private set; } = false;
+
+ public BuildResults? BuildResult { get; private set; } = null;
+
+ public int ErrorCount { get; private set; } = 0;
+
+ public int WarningCount { get; private set; } = 0;
+
+ public bool ErrorsVisible { get; set; } = true;
+ public bool WarningsVisible { get; set; } = true;
+
+ public Texture IconTexture
+ {
+ get
+ {
+ if (!BuildExited)
+ return GetIcon("Stop", "EditorIcons");
+
+ if (BuildResult == BuildResults.Error)
+ return GetIcon("StatusError", "EditorIcons");
+
+ return GetIcon("StatusSuccess", "EditorIcons");
+ }
+ }
+
+ public BuildInfo BuildInfo { get; private set; }
+
+ private void _LoadIssuesFromFile(string csvFile)
+ {
+ using (var file = new Godot.File())
+ {
+ try
+ {
+ Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read);
+
+ if (openError != Error.Ok)
+ return;
+
+ while (!file.EofReached())
+ {
+ string[] csvColumns = file.GetCsvLine();
+
+ if (csvColumns.Length == 1 && csvColumns[0].Empty())
+ return;
+
+ if (csvColumns.Length != 7)
+ {
+ GD.PushError($"Expected 7 columns, got {csvColumns.Length}");
+ continue;
+ }
+
+ var issue = new BuildIssue
+ {
+ Warning = csvColumns[0] == "warning",
+ File = csvColumns[1],
+ Line = int.Parse(csvColumns[2]),
+ Column = int.Parse(csvColumns[3]),
+ Code = csvColumns[4],
+ Message = csvColumns[5],
+ ProjectFile = csvColumns[6]
+ };
+
+ if (issue.Warning)
+ WarningCount += 1;
+ else
+ ErrorCount += 1;
+
+ issues.Add(issue);
+ }
+ }
+ finally
+ {
+ file.Close(); // Disposing it is not enough. We need to call Close()
+ }
+ }
+ }
+
+ private void _IssueActivated(int idx)
+ {
+ if (idx < 0 || idx >= issuesList.GetItemCount())
+ throw new IndexOutOfRangeException("Item list index out of range");
+
+ // Get correct issue idx from issue list
+ int issueIndex = (int) issuesList.GetItemMetadata(idx);
+
+ if (idx < 0 || idx >= issues.Count)
+ throw new IndexOutOfRangeException("Issue index out of range");
+
+ BuildIssue issue = issues[issueIndex];
+
+ if (issue.ProjectFile.Empty() && issue.File.Empty())
+ return;
+
+ string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
+
+ string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
+
+ if (!File.Exists(file))
+ return;
+
+ file = ProjectSettings.LocalizePath(file);
+
+ if (file.StartsWith("res://"))
+ {
+ var script = (Script) ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
+
+ if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
+ Internal.EditorNodeShowScriptScreen();
+ }
+ }
+
+ public void UpdateIssuesList()
+ {
+ issuesList.Clear();
+
+ using (var warningIcon = GetIcon("Warning", "EditorIcons"))
+ using (var errorIcon = GetIcon("Error", "EditorIcons"))
+ {
+ for (int i = 0; i < issues.Count; i++)
+ {
+ BuildIssue issue = issues[i];
+
+ if (!(issue.Warning ? WarningsVisible : ErrorsVisible))
+ continue;
+
+ string tooltip = string.Empty;
+ tooltip += $"Message: {issue.Message}";
+
+ if (!issue.Code.Empty())
+ tooltip += $"\nCode: {issue.Code}";
+
+ tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
+
+ string text = string.Empty;
+
+ if (!issue.File.Empty())
+ {
+ text += $"{issue.File}({issue.Line},{issue.Column}): ";
+
+ tooltip += $"\nFile: {issue.File}";
+ tooltip += $"\nLine: {issue.Line}";
+ tooltip += $"\nColumn: {issue.Column}";
+ }
+
+ if (!issue.ProjectFile.Empty())
+ tooltip += $"\nProject: {issue.ProjectFile}";
+
+ text += issue.Message;
+
+ int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal);
+ string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx);
+ issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon);
+
+ int index = issuesList.GetItemCount() - 1;
+ issuesList.SetItemTooltip(index, tooltip);
+ issuesList.SetItemMetadata(index, i);
+ }
+ }
+ }
+
+ public void OnBuildStart()
+ {
+ BuildExited = false;
+
+ issues.Clear();
+ WarningCount = 0;
+ ErrorCount = 0;
+ UpdateIssuesList();
+
+ GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
+ }
+
+ public void OnBuildExit(BuildResults result)
+ {
+ BuildExited = true;
+ BuildResult = result;
+
+ _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
+ UpdateIssuesList();
+
+ GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
+ }
+
+ public void OnBuildExecFailed(string cause)
+ {
+ BuildExited = true;
+ BuildResult = BuildResults.Error;
+
+ issuesList.Clear();
+
+ var issue = new BuildIssue {Message = cause, Warning = false};
+
+ ErrorCount += 1;
+ issues.Add(issue);
+
+ UpdateIssuesList();
+
+ GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
+ }
+
+ public void RestartBuild()
+ {
+ if (!BuildExited)
+ throw new InvalidOperationException("Build already started");
+
+ BuildManager.RestartBuild(this);
+ }
+
+ public void StopBuild()
+ {
+ if (!BuildExited)
+ throw new InvalidOperationException("Build is not in progress");
+
+ BuildManager.StopBuild(this);
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ issuesList = new ItemList {SizeFlagsVertical = (int) SizeFlags.ExpandFill};
+ issuesList.Connect("item_activated", this, nameof(_IssueActivated));
+ AddChild(issuesList);
+ }
+
+ private BuildTab()
+ {
+ }
+
+ public BuildTab(BuildInfo buildInfo)
+ {
+ BuildInfo = buildInfo;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
new file mode 100644
index 0000000000..c021a9051e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
@@ -0,0 +1,127 @@
+using Godot;
+using System;
+using Godot.Collections;
+using GodotTools.Internals;
+using GodotTools.ProjectEditor;
+using static GodotTools.Internals.Globals;
+using File = GodotTools.Utils.File;
+using Directory = GodotTools.Utils.Directory;
+
+namespace GodotTools
+{
+ public static class CsProjOperations
+ {
+ public static string GenerateGameProject(string dir, string name)
+ {
+ try
+ {
+ return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { });
+ }
+ catch (Exception e)
+ {
+ GD.PushError(e.ToString());
+ return string.Empty;
+ }
+ }
+
+ public static void AddItem(string projectPath, string itemType, string include)
+ {
+ if (!(bool) GlobalDef("mono/project/auto_update_project", true))
+ return;
+
+ ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include);
+ }
+
+ public static void FixApiHintPath(string projectPath)
+ {
+ try
+ {
+ ProjectUtils.FixApiHintPath(projectPath);
+ }
+ catch (Exception e)
+ {
+ GD.PushError(e.ToString());
+ }
+ }
+
+ private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ private static ulong ConvertToTimestamp(this DateTime value)
+ {
+ TimeSpan elapsedTime = value - Epoch;
+ return (ulong) elapsedTime.TotalSeconds;
+ }
+
+ public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+ {
+ if (File.Exists(outputPath))
+ File.Delete(outputPath);
+
+ var oldDict = Internal.GetScriptsMetadataOrNothing();
+ var newDict = new Godot.Collections.Dictionary<string, object>();
+
+ foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
+ {
+ string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
+
+ ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
+
+ if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
+ {
+ var oldFileDict = (Dictionary) oldFileVar;
+
+ if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime))
+ {
+ if (storedModifiedTime == modifiedTime)
+ {
+ // No changes so no need to parse again
+ newDict[projectIncludeFile] = oldFileDict;
+ continue;
+ }
+ }
+ }
+
+ 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/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
new file mode 100644
index 0000000000..4312ca0230
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
@@ -0,0 +1,11 @@
+namespace GodotTools
+{
+ public enum ExternalEditorId
+ {
+ None,
+ VisualStudio, // TODO (Windows-only)
+ VisualStudioForMac, // Mac-only
+ MonoDevelop,
+ VsCode
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
new file mode 100644
index 0000000000..7da7cff933
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -0,0 +1,475 @@
+using Godot;
+using GodotTools.Utils;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using GodotTools.Ides;
+using GodotTools.Internals;
+using GodotTools.ProjectEditor;
+using static GodotTools.Internals.Globals;
+using File = GodotTools.Utils.File;
+using OS = GodotTools.Utils.OS;
+
+namespace GodotTools
+{
+ [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
+ public class GodotSharpEditor : EditorPlugin, ISerializationListener
+ {
+ private EditorSettings editorSettings;
+
+ private PopupMenu menuPopup;
+
+ private AcceptDialog errorDialog;
+ private AcceptDialog aboutDialog;
+ private CheckBox aboutDialogCheckBox;
+
+ private ToolButton bottomPanelBtn;
+
+ public GodotIdeManager GodotIdeManager { get; private set; }
+
+ private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization
+
+ public BottomPanel BottomPanel { get; private set; }
+
+ private bool CreateProjectSolution()
+ {
+ using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
+ {
+ pr.Step("Generating C# project...".TTR());
+
+ string resourceDir = ProjectSettings.GlobalizePath("res://");
+
+ string path = resourceDir;
+ string name = (string) ProjectSettings.GetSetting("application/config/name");
+ if (name.Empty())
+ name = "UnnamedProject";
+
+ string guid = CsProjOperations.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: ".TTR() + e.Message);
+ return false;
+ }
+
+ // Make sure to update the API assemblies if they happen to be missing. Just in
+ // case the user decided to delete them at some point after they were loaded.
+ Internal.UpdateApiAssembliesFromPrebuilt();
+
+ pr.Step("Done".TTR());
+
+ // Here, after all calls to progress_task_step
+ CallDeferred(nameof(_RemoveCreateSlnMenuOption));
+ }
+ else
+ {
+ ShowErrorDialog("Failed to create C# project.".TTR());
+ }
+
+ return true;
+ }
+ }
+
+ 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.BottomPanel.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;
+ }
+ }
+ }
+
+ private enum MenuOptions
+ {
+ CreateSln,
+ AboutCSharp,
+ }
+
+ 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 = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor");
+
+ switch (editor)
+ {
+ case ExternalEditorId.None:
+ // Tells the caller to fallback to the global external editor settings or the built-in editor
+ return Error.Unavailable;
+ case ExternalEditorId.VisualStudio:
+ throw new NotSupportedException();
+ case ExternalEditorId.VisualStudioForMac:
+ goto case ExternalEditorId.MonoDevelop;
+ case ExternalEditorId.MonoDevelop:
+ {
+ string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
+
+ if (line >= 0)
+ GodotIdeManager.SendOpenFile(scriptPath, line + 1, col);
+ else
+ GodotIdeManager.SendOpenFile(scriptPath);
+
+ break;
+ }
+
+ case ExternalEditorId.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;
+ }
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return Error.Ok;
+ }
+
+ public bool OverridesExternalEditor()
+ {
+ return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None;
+ }
+
+ public override bool Build()
+ {
+ return BuildManager.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);
+
+ BottomPanel = new BottomPanel();
+
+ bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR());
+
+ 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".TTR(), (int) MenuOptions.AboutCSharp);
+ 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) * 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, Windows and Android, but not yet to iOS, HTML5 or UWP. " +
+ "Bugs and usability issues will be addressed gradually over future releases, " +
+ "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
+ "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" +
+ " 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!";
+
+ 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))
+ {
+ // Make sure the existing project has Api assembly references configured correctly
+ CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
+ }
+ else
+ {
+ bottomPanelBtn.Hide();
+ menuPopup.AddItem("Create C# solution".TTR(), (int) MenuOptions.CreateSln);
+ }
+
+ menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed));
+
+ var buildButton = new ToolButton
+ {
+ Text = "Build",
+ HintTooltip = "Build solution",
+ FocusMode = Control.FocusModeEnum.None
+ };
+ buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed));
+ AddControlToContainer(CustomControlContainer.Toolbar, buildButton);
+
+ // External editor settings
+ EditorDef("mono/editor/external_editor", ExternalEditorId.None);
+
+ string settingsHintStr = "Disabled";
+
+ if (OS.IsWindows())
+ {
+ settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
+ }
+ else if (OS.IsOSX())
+ {
+ settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" +
+ $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
+ }
+ else if (OS.IsUnix())
+ {
+ settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
+ }
+
+ editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = Variant.Type.Int,
+ ["name"] = "mono/editor/external_editor",
+ ["hint"] = PropertyHint.Enum,
+ ["hint_string"] = settingsHintStr
+ });
+
+ // Export plugin
+ var exportPlugin = new GodotSharpExport();
+ AddExportPlugin(exportPlugin);
+ exportPluginWeak = WeakRef(exportPlugin);
+
+ BuildManager.Initialize();
+
+ GodotIdeManager = new GodotIdeManager();
+ AddChild(GodotIdeManager);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (exportPluginWeak != null)
+ {
+ // We need to dispose our export plugin before the editor destroys EditorSettings.
+ // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid
+ // will be freed after EditorSettings already was, and its device polling thread
+ // will try to access the EditorSettings singleton, resulting in null dereferencing.
+ (exportPluginWeak.GetRef() as GodotSharpExport)?.Dispose();
+
+ exportPluginWeak.Dispose();
+ }
+
+ GodotIdeManager?.Dispose();
+ }
+
+ 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..4f93ef8530
--- /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.Replace("\\", "/"), 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")}");
+ CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
+
+ AddFile(scriptsMetadataPath, scriptsMetadataPath);
+
+ // Turn export features into defines
+ var godotDefines = features;
+
+ if (!BuildManager.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..3c57900873
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -0,0 +1,89 @@
+<?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>
+ <LangVersion>7</LangVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>portable</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="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="ExternalEditorId.cs" />
+ <Compile Include="Ides\GodotIdeManager.cs" />
+ <Compile Include="Ides\GodotIdeServer.cs" />
+ <Compile Include="Ides\MonoDevelop\EditorId.cs" />
+ <Compile Include="Ides\MonoDevelop\Instance.cs" />
+ <Compile Include="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="Internals\Globals.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Build\BuildSystem.cs" />
+ <Compile Include="Utils\Directory.cs" />
+ <Compile Include="Utils\File.cs" />
+ <Compile Include="Utils\NotifyAwaiter.cs" />
+ <Compile Include="Utils\OS.cs" />
+ <Compile Include="GodotSharpEditor.cs" />
+ <Compile Include="BuildManager.cs" />
+ <Compile Include="HotReloadAssemblyWatcher.cs" />
+ <Compile Include="BuildInfo.cs" />
+ <Compile Include="BuildTab.cs" />
+ <Compile Include="BottomPanel.cs" />
+ <Compile Include="GodotSharpExport.cs" />
+ <Compile Include="CsProjOperations.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.IdeConnection\GodotTools.IdeConnection.csproj">
+ <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project>
+ <Name>GodotTools.IdeConnection</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj">
+ <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project>
+ <Name>GodotTools.ProjectEditor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
+ <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
+ <Name>GodotTools.Core</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <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..0f6f5ffadc
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
@@ -0,0 +1,48 @@
+using Godot;
+using GodotTools.Internals;
+using static GodotTools.Internals.Globals;
+
+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) 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/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
new file mode 100644
index 0000000000..9e24138143
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -0,0 +1,166 @@
+using System;
+using System.IO;
+using Godot;
+using GodotTools.IdeConnection;
+using GodotTools.Internals;
+
+namespace GodotTools.Ides
+{
+ public class GodotIdeManager : Node, ISerializationListener
+ {
+ public GodotIdeServer GodotIdeServer { get; private set; }
+
+ private MonoDevelop.Instance monoDevelInstance;
+ private MonoDevelop.Instance vsForMacInstance;
+
+ private GodotIdeServer GetRunningServer()
+ {
+ if (GodotIdeServer != null && !GodotIdeServer.IsDisposed)
+ return GodotIdeServer;
+ StartServer();
+ return GodotIdeServer;
+ }
+
+ public override void _Ready()
+ {
+ StartServer();
+ }
+
+ public void OnBeforeSerialize()
+ {
+ GodotIdeServer?.Dispose();
+ }
+
+ public void OnAfterDeserialize()
+ {
+ StartServer();
+ }
+
+ private ILogger logger;
+
+ protected ILogger Logger
+ {
+ get => logger ?? (logger = new ConsoleLogger());
+ set => logger = value;
+ }
+
+ private void StartServer()
+ {
+ GodotIdeServer?.Dispose();
+ GodotIdeServer = new GodotIdeServer(LaunchIde,
+ OS.GetExecutablePath(),
+ ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir));
+
+ GodotIdeServer.Logger = Logger;
+
+ GodotIdeServer.StartServer();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ GodotIdeServer?.Dispose();
+ }
+
+ private void LaunchIde()
+ {
+ var editor = (ExternalEditorId) GodotSharpEditor.Instance.GetEditorInterface()
+ .GetEditorSettings().GetSetting("mono/editor/external_editor");
+
+ switch (editor)
+ {
+ case ExternalEditorId.None:
+ case ExternalEditorId.VisualStudio:
+ case ExternalEditorId.VsCode:
+ throw new NotSupportedException();
+ case ExternalEditorId.VisualStudioForMac:
+ goto case ExternalEditorId.MonoDevelop;
+ case ExternalEditorId.MonoDevelop:
+ {
+ MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath)
+ {
+ if (Utils.OS.IsOSX() && editor == ExternalEditorId.VisualStudioForMac)
+ {
+ vsForMacInstance = vsForMacInstance ??
+ new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
+ return vsForMacInstance;
+ }
+
+ monoDevelInstance = monoDevelInstance ??
+ new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop);
+ return monoDevelInstance;
+ }
+
+ try
+ {
+ var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath);
+
+ if (!instance.IsRunning)
+ instance.Execute();
+ }
+ catch (FileNotFoundException)
+ {
+ string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
+ GD.PushError($"Cannot find code editor: {editorName}");
+ }
+
+ break;
+ }
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void WriteMessage(string id, params string[] arguments)
+ {
+ GetRunningServer().WriteMessage(new Message(id, arguments));
+ }
+
+ public void SendOpenFile(string file)
+ {
+ WriteMessage("OpenFile", file);
+ }
+
+ public void SendOpenFile(string file, int line)
+ {
+ WriteMessage("OpenFile", file, line.ToString());
+ }
+
+ public void SendOpenFile(string file, int line, int column)
+ {
+ WriteMessage("OpenFile", file, line.ToString(), column.ToString());
+ }
+
+ private class GodotLogger : ILogger
+ {
+ public void LogDebug(string message)
+ {
+ if (OS.IsStdoutVerbose())
+ Console.WriteLine(message);
+ }
+
+ public void LogInfo(string message)
+ {
+ if (OS.IsStdoutVerbose())
+ Console.WriteLine(message);
+ }
+
+ public void LogWarning(string message)
+ {
+ GD.PushWarning(message);
+ }
+
+ public void LogError(string message)
+ {
+ GD.PushError(message);
+ }
+
+ public void LogError(string message, Exception e)
+ {
+ GD.PushError(message + "\n" + e);
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
new file mode 100644
index 0000000000..309b917c71
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeConnection;
+using GodotTools.Internals;
+using GodotTools.Utils;
+using Directory = System.IO.Directory;
+using File = System.IO.File;
+using Thread = System.Threading.Thread;
+
+namespace GodotTools.Ides
+{
+ public class GodotIdeServer : GodotIdeBase
+ {
+ private readonly TcpListener listener;
+ private readonly FileStream metaFile;
+ private readonly Action launchIdeAction;
+ private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>();
+
+ private async Task<bool> AwaitClientConnected()
+ {
+ return await clientConnectedAwaiter.Reset();
+ }
+
+ public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir)
+ : base(projectMetadataDir)
+ {
+ messageHandlers = InitializeMessageHandlers();
+
+ this.launchIdeAction = launchIdeAction;
+
+ // Make sure the directory exists
+ Directory.CreateDirectory(projectMetadataDir);
+
+ // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing...
+ const FileShare metaFileShare = FileShare.ReadWrite;
+
+ metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare);
+
+ listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
+ listener.Start();
+
+ int port = ((IPEndPoint) listener.Server.LocalEndPoint).Port;
+ using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8))
+ {
+ metaFileWriter.WriteLine(port);
+ metaFileWriter.WriteLine(editorExecutablePath);
+ }
+
+ StartServer();
+ }
+
+ public void StartServer()
+ {
+ var serverThread = new Thread(RunServerThread) {Name = "Godot Ide Connection Server"};
+ serverThread.Start();
+ }
+
+ private void RunServerThread()
+ {
+ SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext);
+
+ try
+ {
+ while (!IsDisposed)
+ {
+ TcpClient tcpClient = listener.AcceptTcpClient();
+
+ Logger.LogInfo("Connection open with Ide Client");
+
+ lock (ConnectionLock)
+ {
+ Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage);
+ Connection.Logger = Logger;
+ }
+
+ Connected += () => clientConnectedAwaiter.SetResult(true);
+
+ Connection.Start();
+ }
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted))
+ throw;
+ }
+ }
+
+ public async void WriteMessage(Message message)
+ {
+ async Task LaunchIde()
+ {
+ if (IsConnected)
+ return;
+
+ launchIdeAction();
+ await Task.WhenAny(Task.Delay(10000), AwaitClientConnected());
+ }
+
+ await LaunchIde();
+
+ if (!IsConnected)
+ {
+ Logger.LogError("Cannot write message: Godot Ide Server not connected");
+ return;
+ }
+
+ Connection.WriteMessage(message);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ listener?.Stop();
+
+ metaFile?.Dispose();
+
+ File.Delete(MetaFilePath);
+ }
+ }
+
+ protected virtual bool HandleMessage(Message message)
+ {
+ if (messageHandlers.TryGetValue(message.Id, out var action))
+ {
+ action(message.Arguments);
+ return true;
+ }
+
+ return false;
+ }
+
+ private readonly Dictionary<string, Action<string[]>> messageHandlers;
+
+ private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
+ {
+ return new Dictionary<string, Action<string[]>>
+ {
+ ["Play"] = args =>
+ {
+ switch (args.Length)
+ {
+ case 0:
+ Play();
+ return;
+ case 2:
+ Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1]));
+ return;
+ default:
+ throw new ArgumentException();
+ }
+ },
+ ["ReloadScripts"] = args => ReloadScripts()
+ };
+ }
+
+ private void DispatchToMainThread(Action action)
+ {
+ var d = new SendOrPostCallback(state => action());
+ Godot.Dispatcher.SynchronizationContext.Post(d, null);
+ }
+
+ private void Play()
+ {
+ DispatchToMainThread(() =>
+ {
+ CurrentPlayRequest = new PlayRequest();
+ Internal.EditorRunPlay();
+ CurrentPlayRequest = null;
+ });
+ }
+
+ private void Play(string debuggerHost, int debuggerPort)
+ {
+ DispatchToMainThread(() =>
+ {
+ CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort);
+ Internal.EditorRunPlay();
+ CurrentPlayRequest = null;
+ });
+ }
+
+ private void ReloadScripts()
+ {
+ DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts);
+ }
+
+ public PlayRequest? CurrentPlayRequest { get; private set; }
+
+ public struct PlayRequest
+ {
+ public bool HasDebugger { get; }
+ public string DebuggerHost { get; }
+ public int DebuggerPort { get; }
+
+ public PlayRequest(string debuggerHost, int debuggerPort)
+ {
+ HasDebugger = true;
+ DebuggerHost = debuggerHost;
+ DebuggerPort = debuggerPort;
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs
new file mode 100644
index 0000000000..1dfc91d6d1
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs
@@ -0,0 +1,8 @@
+namespace GodotTools.Ides.MonoDevelop
+{
+ public enum EditorId
+ {
+ MonoDevelop = 0,
+ VisualStudioForMac = 1
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
new file mode 100644
index 0000000000..1fdccf5bbd
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
@@ -0,0 +1,131 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Diagnostics;
+using GodotTools.Internals;
+using GodotTools.Utils;
+
+namespace GodotTools.Ides.MonoDevelop
+{
+ public class Instance
+ {
+ private readonly string solutionFile;
+ private readonly EditorId editorId;
+
+ private Process process;
+
+ public bool IsRunning => process != null && !process.HasExited;
+
+ public void Execute()
+ {
+ bool newWindow = process == null || process.HasExited;
+
+ var args = new List<string>();
+
+ string command;
+
+ if (OS.IsOSX())
+ {
+ string bundleId = BundleIds[editorId];
+
+ if (Internal.IsOsxAppBundleInstalled(bundleId))
+ {
+ command = "open";
+
+ args.Add("-b");
+ args.Add(bundleId);
+
+ // The 'open' process must wait until the application finishes
+ if (newWindow)
+ args.Add("--wait-apps");
+
+ args.Add("--args");
+ }
+ else
+ {
+ command = OS.PathWhich(ExecutableNames[editorId]);
+ }
+ }
+ else
+ {
+ command = OS.PathWhich(ExecutableNames[editorId]);
+ }
+
+ args.Add("--ipc-tcp");
+
+ if (newWindow)
+ args.Add("\"" + Path.GetFullPath(solutionFile) + "\"");
+
+ if (command == null)
+ throw new FileNotFoundException();
+
+ if (newWindow)
+ {
+ process = Process.Start(new ProcessStartInfo
+ {
+ FileName = command,
+ Arguments = string.Join(" ", args),
+ UseShellExecute = true
+ });
+ }
+ else
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = command,
+ Arguments = string.Join(" ", args),
+ UseShellExecute = true
+ })?.Dispose();
+ }
+ }
+
+ public Instance(string solutionFile, EditorId editorId)
+ {
+ if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX())
+ throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform");
+
+ this.solutionFile = solutionFile;
+ this.editorId = editorId;
+ }
+
+ private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames;
+ private static readonly IReadOnlyDictionary<EditorId, string> BundleIds;
+
+ static Instance()
+ {
+ if (OS.IsOSX())
+ {
+ ExecutableNames = new Dictionary<EditorId, string>
+ {
+ // Rely on PATH
+ {EditorId.MonoDevelop, "monodevelop"},
+ {EditorId.VisualStudioForMac, "VisualStudio"}
+ };
+ BundleIds = new Dictionary<EditorId, string>
+ {
+ // TODO EditorId.MonoDevelop
+ {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
+ };
+ }
+ else if (OS.IsWindows())
+ {
+ ExecutableNames = 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"}
+ };
+ }
+ else if (OS.IsUnix())
+ {
+ ExecutableNames = new Dictionary<EditorId, string>
+ {
+ // Rely on PATH
+ {EditorId.MonoDevelop, "monodevelop"}
+ };
+ }
+ }
+ }
+}
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/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
new file mode 100644
index 0000000000..793f84fd77
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
@@ -0,0 +1,33 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace GodotTools.Internals
+{
+ public static class Globals
+ {
+ 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);
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static string TTR(this string text) => internal_TTR(text);
+
+ // 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_TTR(string text);
+ }
+}
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..7783576910
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -0,0 +1,114 @@
+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 string UpdateApiAssembliesFromPrebuilt() =>
+ internal_UpdateApiAssembliesFromPrebuilt();
+
+ 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 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();
+
+ public static void EditorRunPlay() => internal_EditorRunPlay();
+
+ public static void EditorRunStop() => internal_EditorRunStop();
+
+ public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
+
+ // Internal Calls
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string internal_UpdateApiAssembliesFromPrebuilt();
+
+ [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_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();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_EditorRunPlay();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_EditorRunStop();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_ScriptEditorDebugger_ReloadScripts();
+ }
+}
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/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..e3c2c822a5
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+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)
+ {
+ T result = predicate(elem);
+ if (result != null)
+ return result;
+ }
+
+ return orElse;
+ }
+
+ public static IEnumerable<string> EnumerateLines(this TextReader textReader)
+ {
+ string line;
+ while ((line = textReader.ReadLine()) != null)
+ yield return line;
+ }
+ }
+}
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/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs
new file mode 100644
index 0000000000..700b786752
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace GodotTools.Utils
+{
+ public sealed class NotifyAwaiter<T> : INotifyCompletion
+ {
+ private Action continuation;
+ private Exception exception;
+ private T result;
+
+ public bool IsCompleted { get; private set; }
+
+ public T GetResult()
+ {
+ if (exception != null)
+ throw exception;
+ return result;
+ }
+
+ public void OnCompleted(Action continuation)
+ {
+ if (this.continuation != null)
+ throw new InvalidOperationException("This awaiter has already been listened");
+ this.continuation = continuation;
+ }
+
+ public void SetResult(T result)
+ {
+ if (IsCompleted)
+ throw new InvalidOperationException("This awaiter is already completed");
+
+ IsCompleted = true;
+ this.result = result;
+
+ continuation?.Invoke();
+ }
+
+ public void SetException(Exception exception)
+ {
+ if (IsCompleted)
+ throw new InvalidOperationException("This awaiter is already completed");
+
+ IsCompleted = true;
+ this.exception = exception;
+
+ continuation?.Invoke();
+ }
+
+ public NotifyAwaiter<T> Reset()
+ {
+ continuation = null;
+ exception = null;
+ result = default(T);
+ IsCompleted = false;
+ return this;
+ }
+
+ public NotifyAwaiter<T> GetAwaiter()
+ {
+ return this;
+ }
+ }
+}
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 308c54ecb3..28cab2ab61 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -30,7 +30,7 @@
#include "bindings_generator.h"
-#ifdef DEBUG_METHODS_ENABLED
+#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
#include "core/engine.h"
#include "core/global_constants.h"
@@ -47,7 +47,6 @@
#include "../utils/path_utils.h"
#include "../utils/string_utils.h"
#include "csharp_project.h"
-#include "net_solution.h"
#define CS_INDENT " " // 4 whitespaces
@@ -81,6 +80,7 @@
#define ICALL_GET_METHODBIND ICALL_PREFIX "Object_ClassDB_get_method"
#define C_LOCAL_RET "ret"
+#define C_LOCAL_VARARG_RET "vararg_ret"
#define C_LOCAL_PTRCALL_ARGS "call_args"
#define C_MACRO_OBJECT_CONSTRUCT "GODOTSHARP_INSTANCE_OBJECT"
@@ -97,13 +97,19 @@
#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(3)
+#define BINDINGS_GENERATOR_VERSION UINT32_C(9)
-const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in = %1;\n";
+const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n");
-bool BindingsGenerator::verbose_output = false;
+static String fix_doc_description(const String &p_bbcode) {
-BindingsGenerator *BindingsGenerator::singleton = NULL;
+ // This seems to be the correct way to do this. It's the same EditorHelp does.
+
+ return p_bbcode.dedent()
+ .replace("\t", "")
+ .replace("\r", "")
+ .strip_edges();
+}
static String snake_to_pascal_case(const String &p_identifier, bool p_input_is_upper = false) {
@@ -173,23 +179,525 @@ static String snake_to_camel_case(const String &p_identifier, bool p_input_is_up
return ret;
}
-String BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) {
+String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) {
+
+ // Based on the version in EditorHelp
+
+ if (p_bbcode.empty())
+ return String();
+
+ DocData *doc = EditorHelp::get_doc_data();
+
+ String bbcode = p_bbcode;
+
+ StringBuilder xml_output;
+
+ xml_output.append("<para>");
+
+ List<String> tag_stack;
+ bool code_tag = false;
+
+ int pos = 0;
+ while (pos < bbcode.length()) {
+ int brk_pos = bbcode.find("[", pos);
+
+ if (brk_pos < 0)
+ brk_pos = bbcode.length();
+
+ if (brk_pos > pos) {
+ String text = bbcode.substr(pos, brk_pos - pos);
+ if (code_tag || tag_stack.size() > 0) {
+ xml_output.append(text.xml_escape());
+ } else {
+ Vector<String> lines = text.split("\n");
+ for (int i = 0; i < lines.size(); i++) {
+ if (i != 0)
+ xml_output.append("<para>");
+
+ xml_output.append(lines[i].xml_escape());
+
+ if (i != lines.size() - 1)
+ xml_output.append("</para>\n");
+ }
+ }
+ }
+
+ if (brk_pos == bbcode.length())
+ break; // nothing else to add
+
+ int brk_end = bbcode.find("]", brk_pos + 1);
+
+ if (brk_end == -1) {
+ String text = bbcode.substr(brk_pos, bbcode.length() - brk_pos);
+ if (code_tag || tag_stack.size() > 0) {
+ xml_output.append(text.xml_escape());
+ } else {
+ Vector<String> lines = text.split("\n");
+ for (int i = 0; i < lines.size(); i++) {
+ if (i != 0)
+ xml_output.append("<para>");
+
+ xml_output.append(lines[i].xml_escape());
+
+ if (i != lines.size() - 1)
+ xml_output.append("</para>\n");
+ }
+ }
+
+ break;
+ }
+
+ String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
+
+ if (tag.begins_with("/")) {
+ bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length());
+
+ if (!tag_ok) {
+ xml_output.append("[");
+ pos = brk_pos + 1;
+ continue;
+ }
+
+ tag_stack.pop_front();
+ pos = brk_end + 1;
+ code_tag = false;
+
+ if (tag == "/url") {
+ xml_output.append("</a>");
+ } else if (tag == "/code") {
+ xml_output.append("</c>");
+ } else if (tag == "/codeblock") {
+ xml_output.append("</code>");
+ }
+ } else if (code_tag) {
+ xml_output.append("[");
+ pos = brk_pos + 1;
+ } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ")) {
+ String link_target = tag.substr(tag.find(" ") + 1, tag.length());
+ String link_tag = tag.substr(0, tag.find(" "));
+
+ Vector<String> link_target_parts = link_target.split(".");
+
+ if (link_target_parts.size() <= 0 || link_target_parts.size() > 2) {
+ ERR_PRINTS("Invalid reference format: '" + tag + "'.");
+
+ xml_output.append("<c>");
+ xml_output.append(tag);
+ xml_output.append("</c>");
+
+ pos = brk_end + 1;
+ continue;
+ }
+
+ const TypeInterface *target_itype;
+ StringName target_cname;
+
+ if (link_target_parts.size() == 2) {
+ target_itype = _get_type_or_null(TypeReference(link_target_parts[0]));
+ if (!target_itype) {
+ target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0]));
+ }
+ target_cname = link_target_parts[1];
+ } else {
+ target_itype = p_itype;
+ target_cname = link_target_parts[0];
+ }
+
+ if (link_tag == "method") {
+ if (!target_itype || !target_itype->is_object_type) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ if (target_itype) {
+ OS::get_singleton()->print("Cannot resolve method reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data());
+ } else {
+ OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", link_target.utf8().get_data());
+ }
+ }
+
+ // TODO Map what we can
+ xml_output.append("<c>");
+ xml_output.append(link_target);
+ xml_output.append("</c>");
+ } else {
+ const MethodInterface *target_imethod = target_itype->find_method_by_name(target_cname);
+
+ if (target_imethod) {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_itype->proxy_name);
+ xml_output.append(".");
+ xml_output.append(target_imethod->proxy_name);
+ xml_output.append("\"/>");
+ }
+ }
+ } else if (link_tag == "member") {
+ if (!target_itype || !target_itype->is_object_type) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ if (target_itype) {
+ OS::get_singleton()->print("Cannot resolve member reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data());
+ } else {
+ OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", link_target.utf8().get_data());
+ }
+ }
+
+ // TODO Map what we can
+ xml_output.append("<c>");
+ xml_output.append(link_target);
+ xml_output.append("</c>");
+ } else {
+ const PropertyInterface *target_iprop = target_itype->find_property_by_name(target_cname);
+
+ if (target_iprop) {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_itype->proxy_name);
+ xml_output.append(".");
+ xml_output.append(target_iprop->proxy_name);
+ xml_output.append("\"/>");
+ }
+ }
+ } else if (link_tag == "signal") {
+ // We do not declare signals in any way in C#, so there is nothing to reference
+ xml_output.append("<c>");
+ xml_output.append(link_target);
+ xml_output.append("</c>");
+ } else if (link_tag == "enum") {
+ StringName search_cname = !target_itype ? target_cname :
+ StringName(target_itype->name + "." + (String)target_cname);
+
+ const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(search_cname);
+
+ if (!enum_match && search_cname != target_cname) {
+ enum_match = enum_types.find(target_cname);
+ }
+
+ if (enum_match) {
+ const TypeInterface &target_enum_itype = enum_match->value();
+
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any
+ xml_output.append("\"/>");
+ } else {
+ ERR_PRINTS("Cannot resolve enum reference in documentation: '" + link_target + "'.");
+
+ xml_output.append("<c>");
+ xml_output.append(link_target);
+ xml_output.append("</c>");
+ }
+ } else if (link_tag == "const") {
+ if (!target_itype || !target_itype->is_object_type) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ if (target_itype) {
+ OS::get_singleton()->print("Cannot resolve constant reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data());
+ } else {
+ OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", link_target.utf8().get_data());
+ }
+ }
+
+ // TODO Map what we can
+ xml_output.append("<c>");
+ xml_output.append(link_target);
+ xml_output.append("</c>");
+ } else if (!target_itype && target_cname == name_cache.type_at_GlobalScope) {
+ String target_name = (String)target_cname;
+
+ // Try to find as a global constant
+ const ConstantInterface *target_iconst = find_constant_by_name(target_name, global_constants);
+
+ if (target_iconst) {
+ // Found global constant
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS ".");
+ xml_output.append(target_iconst->proxy_name);
+ xml_output.append("\"/>");
+ } else {
+ // Try to find as global enum constant
+ const EnumInterface *target_ienum = NULL;
+
+ for (const List<EnumInterface>::Element *E = global_enums.front(); E; E = E->next()) {
+ target_ienum = &E->get();
+ target_iconst = find_constant_by_name(target_name, target_ienum->constants);
+ if (target_iconst)
+ break;
+ }
+
+ if (target_iconst) {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_ienum->cname);
+ xml_output.append(".");
+ xml_output.append(target_iconst->proxy_name);
+ xml_output.append("\"/>");
+ } else {
+ ERR_PRINTS("Cannot resolve global constant reference in documentation: '" + link_target + "'.");
+
+ xml_output.append("<c>");
+ xml_output.append(link_target);
+ xml_output.append("</c>");
+ }
+ }
+ } else {
+ String target_name = (String)target_cname;
+
+ // Try to find the constant in the current class
+ const ConstantInterface *target_iconst = find_constant_by_name(target_name, target_itype->constants);
+
+ if (target_iconst) {
+ // Found constant in current class
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_itype->proxy_name);
+ xml_output.append(".");
+ xml_output.append(target_iconst->proxy_name);
+ xml_output.append("\"/>");
+ } else {
+ // Try to find as enum constant in the current class
+ const EnumInterface *target_ienum = NULL;
+
+ for (const List<EnumInterface>::Element *E = target_itype->enums.front(); E; E = E->next()) {
+ target_ienum = &E->get();
+ target_iconst = find_constant_by_name(target_name, target_ienum->constants);
+ if (target_iconst)
+ break;
+ }
+
+ if (target_iconst) {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_itype->proxy_name);
+ xml_output.append(".");
+ xml_output.append(target_ienum->cname);
+ xml_output.append(".");
+ xml_output.append(target_iconst->proxy_name);
+ xml_output.append("\"/>");
+ } else {
+ ERR_PRINTS("Cannot resolve constant reference in documentation: '" + link_target + "'.");
+
+ xml_output.append("<c>");
+ xml_output.append(link_target);
+ xml_output.append("</c>");
+ }
+ }
+ }
+ }
+
+ pos = brk_end + 1;
+ } else if (doc->class_list.has(tag)) {
+ if (tag == "Array" || tag == "Dictionary") {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE_COLLECTIONS ".");
+ xml_output.append(tag);
+ xml_output.append("\"/>");
+ } else if (tag == "bool" || tag == "int") {
+ xml_output.append("<see cref=\"");
+ xml_output.append(tag);
+ xml_output.append("\"/>");
+ } else if (tag == "float") {
+ xml_output.append("<see cref=\""
+#ifdef REAL_T_IS_DOUBLE
+ "double"
+#else
+ "float"
+#endif
+ "\"/>");
+ } else if (tag == "Variant") {
+ // We use System.Object for Variant, so there is no Variant type in C#
+ xml_output.append("<c>Variant</c>");
+ } else if (tag == "String") {
+ xml_output.append("<see cref=\"string\"/>");
+ } else if (tag == "Nil") {
+ xml_output.append("<see langword=\"null\"/>");
+ } else if (tag.begins_with("@")) {
+ // @GlobalScope, @GDScript, etc
+ xml_output.append("<c>");
+ xml_output.append(tag);
+ xml_output.append("</c>");
+ } else if (tag == "PoolByteArray") {
+ xml_output.append("<see cref=\"byte\"/>");
+ } else if (tag == "PoolIntArray") {
+ xml_output.append("<see cref=\"int\"/>");
+ } else if (tag == "PoolRealArray") {
+#ifdef REAL_T_IS_DOUBLE
+ xml_output.append("<see cref=\"double\"/>");
+#else
+ xml_output.append("<see cref=\"float\"/>");
+#endif
+ } else if (tag == "PoolStringArray") {
+ xml_output.append("<see cref=\"string\"/>");
+ } else if (tag == "PoolVector2Array") {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>");
+ } else if (tag == "PoolVector3Array") {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>");
+ } else if (tag == "PoolColorArray") {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>");
+ } else {
+ const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));
+
+ if (!target_itype) {
+ target_itype = _get_type_or_null(TypeReference("_" + tag));
+ }
+
+ if (target_itype) {
+ xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
+ xml_output.append(target_itype->proxy_name);
+ xml_output.append("\"/>");
+ } else {
+ ERR_PRINTS("Cannot resolve type reference in documentation: '" + tag + "'.");
+
+ xml_output.append("<c>");
+ xml_output.append(tag);
+ xml_output.append("</c>");
+ }
+ }
+
+ pos = brk_end + 1;
+ } else if (tag == "b") {
+ // bold is not supported in xml comments
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag == "i") {
+ // italics is not supported in xml comments
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag == "code") {
+ xml_output.append("<c>");
+
+ code_tag = true;
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag == "codeblock") {
+ xml_output.append("<code>");
+
+ code_tag = true;
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag == "center") {
+ // center is alignment not supported in xml comments
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag == "br") {
+ xml_output.append("\n"); // FIXME: Should use <para> instead. Luckily this tag isn't used for now.
+ pos = brk_end + 1;
+ } else if (tag == "u") {
+ // underline is not supported in xml comments
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag == "s") {
+ // strikethrough is not supported in xml comments
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag == "url") {
+ int end = bbcode.find("[", brk_end);
+ if (end == -1)
+ end = bbcode.length();
+ String url = bbcode.substr(brk_end + 1, end - brk_end - 1);
+ xml_output.append("<a href=\"");
+ xml_output.append(url);
+ xml_output.append("\">");
+ xml_output.append(url);
+
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
+ } else if (tag.begins_with("url=")) {
+ String url = tag.substr(4, tag.length());
+ xml_output.append("<a href=\"");
+ xml_output.append(url);
+ xml_output.append("\">");
+
+ pos = brk_end + 1;
+ tag_stack.push_front("url");
+ } else if (tag == "img") {
+ int end = bbcode.find("[", brk_end);
+ if (end == -1)
+ end = bbcode.length();
+ String image = bbcode.substr(brk_end + 1, end - brk_end - 1);
+
+ // Not supported. Just append the bbcode.
+ xml_output.append("[img]");
+ xml_output.append(image);
+ xml_output.append("[/img]");
+
+ pos = end;
+ tag_stack.push_front(tag);
+ } else if (tag.begins_with("color=")) {
+ // Not supported.
+ pos = brk_end + 1;
+ tag_stack.push_front("color");
+ } else if (tag.begins_with("font=")) {
+ // Not supported.
+ pos = brk_end + 1;
+ tag_stack.push_front("font");
+ } else {
+ xml_output.append("["); // ignore
+ pos = brk_pos + 1;
+ }
+ }
+
+ xml_output.append("</para>");
+
+ return xml_output.as_string();
+}
+
+int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) {
CRASH_COND(p_ienum.constants.empty());
- const List<ConstantInterface>::Element *front = p_ienum.constants.front();
- int candidate_len = front->get().name.length();
+ const ConstantInterface &front_iconstant = p_ienum.constants.front()->get();
+ Vector<String> front_parts = front_iconstant.name.split("_", /* p_allow_empty: */ true);
+ int candidate_len = front_parts.size() - 1;
+
+ if (candidate_len == 0)
+ return 0;
- for (const List<ConstantInterface>::Element *E = front->next(); E; E = E->next()) {
- int j = 0;
- for (j = 0; j < candidate_len && j < E->get().name.length(); j++) {
- if (front->get().name[j] != E->get().name[j])
- break;
+ for (const List<ConstantInterface>::Element *E = p_ienum.constants.front()->next(); E; E = E->next()) {
+ const ConstantInterface &iconstant = E->get();
+
+ Vector<String> parts = iconstant.name.split("_", /* p_allow_empty: */ true);
+
+ int i;
+ for (i = 0; i < candidate_len && i < parts.size(); i++) {
+ if (front_parts[i] != parts[i]) {
+ // HARDCODED: Some Flag enums have the prefix 'FLAG_' for everything except 'FLAGS_DEFAULT' (same for 'METHOD_FLAG_' and'METHOD_FLAGS_DEFAULT').
+ bool hardcoded_exc = (i == candidate_len - 1 && ((front_parts[i] == "FLAGS" && parts[i] == "FLAG") || (front_parts[i] == "FLAG" && parts[i] == "FLAGS")));
+ if (!hardcoded_exc)
+ break;
+ }
}
- candidate_len = j;
+ candidate_len = i;
+
+ if (candidate_len == 0)
+ return 0;
}
- return front->get().name.substr(0, candidate_len);
+ return candidate_len;
+}
+
+void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumInterface &p_ienum, int p_prefix_length) {
+
+ if (p_prefix_length > 0) {
+ for (List<ConstantInterface>::Element *E = p_ienum.constants.front(); E; E = E->next()) {
+ int curr_prefix_length = p_prefix_length;
+
+ ConstantInterface &curr_const = E->get();
+
+ String constant_name = curr_const.name;
+
+ Vector<String> parts = constant_name.split("_", /* p_allow_empty: */ true);
+
+ if (parts.size() <= curr_prefix_length)
+ continue;
+
+ if (parts[curr_prefix_length][0] >= '0' && parts[curr_prefix_length][0] <= '9') {
+ // The name of enum constants may begin with a numeric digit when strip from the enum prefix,
+ // so we make the prefix for this constant one word shorter in those cases.
+ for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) {
+ if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9')
+ break;
+ }
+ }
+
+ constant_name = "";
+ for (int i = curr_prefix_length; i < parts.size(); i++) {
+ if (i > curr_prefix_length)
+ constant_name += "_";
+ constant_name += parts[i];
+ }
+
+ curr_const.proxy_name = snake_to_pascal_case(constant_name, true);
+ }
+ }
}
void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) {
@@ -244,44 +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("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
- p_output.push_back(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{");
+ p_output.append("\n#pragma warning disable CS1591 // Disable warning: "
+ "'Missing XML comment for publicly visible type or member'\n");
+
+ 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()) {
- p_output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- Vector<String> description_lines = iconstant.const_doc->description.split("\n");
+ if (summary_lines.size()) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
- for (int i = 0; i < description_lines.size(); i++) {
- String description_line = description_lines[i].strip_edges();
- if (description_line.size()) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(description_line.xml_escape());
- p_output.push_back("\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- }
- p_output.push_back(INDENT2 "/// </summary>");
+ p_output.append(INDENT2 "/// </summary>");
+ }
}
- p_output.push_back(MEMBER_BEGIN "public const int ");
- p_output.push_back(iconstant.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
@@ -301,108 +812,84 @@ 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 *E = ienum.constants.front(); E; E = E->next()) {
- const ConstantInterface &iconstant = E->get();
+ 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()) {
- p_output.push_back(INDENT2 "/// <summary>\n");
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- Vector<String> description_lines = iconstant.const_doc->description.split("\n");
+ if (summary_lines.size()) {
+ p_output.append(INDENT2 "/// <summary>\n");
- for (int i = 0; i < description_lines.size(); i++) {
- String description_line = description_lines[i].strip_edges();
- if (description_line.size()) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(description_line.xml_escape());
- p_output.push_back("\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- }
-
- p_output.push_back(INDENT2 "/// </summary>\n");
- }
-
- String constant_name = iconstant.name;
- if (!ienum.prefix.empty() && constant_name.begins_with(ienum.prefix)) {
- constant_name = constant_name.substr(ienum.prefix.length(), constant_name.length());
- }
-
- if (constant_name[0] >= '0' && constant_name[0] <= '9') {
- // The name of enum constants may begin with a numeric digit when strip from the enum prefix,
- // so we make the prefix one word shorter in those cases.
- int i = 0;
- for (i = ienum.prefix.length() - 1; i >= 0; i--) {
- if (ienum.prefix[i] >= 'A' && ienum.prefix[i] <= 'Z')
- break;
+ p_output.append(INDENT2 "/// </summary>\n");
}
- constant_name = ienum.prefix.substr(i, ienum.prefix.length()) + constant_name;
}
- p_output.push_back(INDENT2);
- p_output.push_back(constant_name);
- p_output.push_back(" = ");
- p_output.push_back(itos(iconstant.value));
- p_output.push_back(E != 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.append("\n#pragma warning restore CS1591\n");
}
-Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bool p_verbose_output) {
+Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
- verbose_output = p_verbose_output;
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
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);
- ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
+ if (!DirAccess::exists(p_proj_dir)) {
+ Error err = da->make_dir_recursive(p_proj_dir);
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create directory '" + p_proj_dir + "'.");
}
- da->change_dir(p_output_dir);
+ da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
- String core_dir = path_join(p_output_dir, "Core");
- String obj_type_dir = path_join(p_output_dir, "ObjectType");
-
- Vector<String> compile_items;
-
- NETSolution solution(API_ASSEMBLY_NAME);
-
- if (!solution.set_path(p_output_dir))
- return ERR_FILE_NOT_FOUND;
+ 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()) {
@@ -411,7 +898,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo
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)
@@ -420,19 +907,19 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo
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);
@@ -441,8 +928,6 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo
String output_dir = output_file.get_base_dir();
if (!DirAccess::exists(output_dir)) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(output_dir));
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
@@ -452,31 +937,31 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo
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())
@@ -486,57 +971,37 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo
#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(p_output_dir, compile_items);
-
- solution.add_new_project(API_ASSEMBLY_NAME, guid);
-
- Error sln_error = solution.save();
- if (sln_error != OK) {
- ERR_PRINT("Could not to save .NET solution.");
- return sln_error;
- }
-
- 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_output_dir, const String &p_core_dll_path, bool p_verbose_output) {
+Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
- verbose_output = p_verbose_output;
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
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(p_proj_dir)) {
+ Error err = da->make_dir_recursive(p_proj_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
- da->change_dir(p_output_dir);
+ da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
- String core_dir = path_join(p_output_dir, "Core");
- String obj_type_dir = path_join(p_output_dir, "ObjectType");
-
- Vector<String> compile_items;
-
- NETSolution solution(EDITOR_API_ASSEMBLY_NAME);
-
- if (!solution.set_path(p_output_dir))
- return ERR_FILE_NOT_FOUND;
+ 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();
@@ -544,7 +1009,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_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)
@@ -553,32 +1018,32 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_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())
@@ -588,35 +1053,72 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_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);
+ r_compile_items.push_back(internal_methods_file);
- String guid = CSharpProject::generate_editor_api_project(p_output_dir, p_core_dll_path, compile_items);
+ return OK;
+}
+
+Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
+
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
+ String output_dir = path::abspath(path::realpath(p_output_dir));
+
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
+
+ if (!DirAccess::exists(output_dir)) {
+ Error err = da->make_dir_recursive(output_dir);
+ ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
+ }
- solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, guid);
+ Error proj_err;
- Error sln_error = solution.save();
- if (sln_error != OK) {
- ERR_PRINT("Could not to save .NET solution.");
- return sln_error;
+ // Generate GodotSharp source files
+
+ String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME);
+ Vector<String> core_compile_items;
+
+ 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;
+ }
+
+ // 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;
}
- if (verbose_output)
- OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n");
+ // 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;
}
// FIXME: There are some members that hide other inherited members.
// - In the case of both members being the same kind, the new one must be declared
-// explicitly as `new` to avoid the warning (and we must print a message about it).
+// explicitly as 'new' to avoid the warning (and we must print a message about it).
// - In the case of both members being of a different kind, then the new one must
// be renamed to avoid the name collision (and we must print a warning about it).
// - Csc warning e.g.:
@@ -638,59 +1140,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(String("Generating " + itype.proxy_name + ".cs...\n").utf8());
+ _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.append("using System;\n"); // IntPtr
+ output.append("using System.Diagnostics;\n"); // DebuggerBrowsable
+
+ 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("using System;\n"); // IntPtr
- output.push_back("\n#pragma warning disable CS1591 // Disable warning: "
- "'Missing XML comment for publicly visible type or member'\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()) {
- output.push_back(INDENT1 "/// <summary>\n");
+ String xml_summary = bbcode_to_xml(fix_doc_description(class_doc->description), &itype);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- Vector<String> description_lines = class_doc->description.split("\n");
+ if (summary_lines.size()) {
+ output.append(INDENT1 "/// <summary>\n");
- for (int i = 0; i < description_lines.size(); i++) {
- String description_line = description_lines[i].strip_edges();
- if (description_line.size()) {
- output.push_back(INDENT1 "/// ");
- output.push_back(description_line.xml_escape());
- output.push_back("\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ 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);
+ 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) {
@@ -700,31 +1207,31 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
const ConstantInterface &iconstant = E->get();
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
- output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- Vector<String> description_lines = iconstant.const_doc->description.split("\n");
+ if (summary_lines.size()) {
+ output.append(MEMBER_BEGIN "/// <summary>\n");
- for (int i = 0; i < description_lines.size(); i++) {
- String description_line = description_lines[i].strip_edges();
- if (description_line.size()) {
- output.push_back(INDENT2 "/// ");
- output.push_back(description_line.xml_escape());
- output.push_back("\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ 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.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
@@ -733,55 +1240,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 *E = ienum.constants.front(); E; E = E->next()) {
- const ConstantInterface &iconstant = E->get();
+ 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()) {
- output.push_back(INDENT3 "/// <summary>\n");
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- Vector<String> description_lines = iconstant.const_doc->description.split("\n");
+ if (summary_lines.size()) {
+ output.append(INDENT3 "/// <summary>\n");
- for (int i = 0; i < description_lines.size(); i++) {
- String description_line = description_lines[i].strip_edges();
- if (description_line.size()) {
- output.push_back(INDENT3 "/// ");
- output.push_back(description_line.xml_escape());
- output.push_back("\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ output.append(INDENT3 "/// ");
+ output.append(summary_lines[i]);
+ output.append("\n");
}
- }
-
- output.push_back(INDENT3 "/// </summary>\n");
- }
-
- String constant_name = iconstant.name;
-
- if (!ienum.prefix.empty() && constant_name.begins_with(ienum.prefix)) {
- constant_name = constant_name.substr(ienum.prefix.length(), constant_name.length());
- }
- if (constant_name[0] >= '0' && constant_name[0] <= '9') {
- // The name of enum constants may begin with a numeric digit when strip from the enum prefix,
- // so we make the prefix one word shorter in those cases.
- int i = 0;
- for (i = ienum.prefix.length() - 1; i >= 0; i--) {
- if (ienum.prefix[i] >= 'A' && ienum.prefix[i] <= 'Z')
- break;
+ output.append(INDENT3 "/// </summary>\n");
}
- constant_name = ienum.prefix.substr(i, ienum.prefix.length()) + constant_name;
}
- output.push_back(INDENT3);
- output.push_back(constant_name);
- output.push_back(" = ");
- output.push_back(itos(iconstant.value));
- output.push_back(E != 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
@@ -789,68 +1279,73 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
for (const List<PropertyInterface>::Element *E = itype.properties.front(); E; E = E->next()) {
const PropertyInterface &iprop = E->get();
Error prop_err = _generate_cs_property(itype, iprop, output);
- if (prop_err != OK) {
- ERR_EXPLAIN("Failed to generate property '" + iprop.cname.operator String() +
- "' for class '" + itype.name + "'");
- ERR_FAIL_V(prop_err);
- }
+ ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err,
+ "Failed to generate property '" + iprop.cname.operator String() +
+ "' for class '" + itype.name + "'.");
}
}
+ // TODO: BINDINGS_NATIVE_NAME_FIELD should be StringName, once we support it in C#
+
if (itype.is_singleton) {
// Add the type name and the singleton pointer as static fields
- 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;
for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) {
const MethodInterface &imethod = E->get();
Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output);
- if (method_err != OK) {
- ERR_EXPLAIN("Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'");
- ERR_FAIL_V(method_err);
- }
+ ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
+ "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");
}
if (itype.is_singleton) {
@@ -867,15 +1362,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");
+ 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);
@@ -920,86 +1417,96 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
const TypeInterface *prop_itype = _get_type_or_null(proptype_name);
ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found
- String prop_proxy_name = escape_csharp_keyword(snake_to_pascal_case(p_iprop.cname));
-
- // Prevent property and enclosing type from sharing the same name
- if (prop_proxy_name == p_itype.proxy_name) {
- if (verbose_output) {
- WARN_PRINTS("Name of property `" + prop_proxy_name + "` is ambiguous with the name of its class `" +
- p_itype.proxy_name + "`. Renaming property to `" + prop_proxy_name + "_`");
- }
-
- prop_proxy_name += "_";
- }
-
if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) {
- p_output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- Vector<String> description_lines = p_iprop.prop_doc->description.split("\n");
+ if (summary_lines.size()) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
- for (int i = 0; i < description_lines.size(); i++) {
- String description_line = description_lines[i].strip_edges();
- if (description_line.size()) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(description_line.xml_escape());
- p_output.push_back("\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- }
- p_output.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(prop_proxy_name.replace("/", "__"));
- 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);
@@ -1011,7 +1518,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()) {
@@ -1077,7 +1584,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
icall_params += arg_type->cs_in.empty() ? arg_in : sformat(arg_type->cs_in, arg_in);
- default_args_doc.push_back(INDENT2 "/// <param name=\"" + iarg.name + "\">If the param is null, then the default value is " + def_arg + "</param>\n");
+ // Apparently the name attribute must not include the @
+ String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name;
+
+ default_args_doc.append(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);
}
@@ -1086,61 +1596,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 "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()) {
- p_output.push_back(MEMBER_BEGIN "/// <summary>\n");
+ String xml_summary = bbcode_to_xml(fix_doc_description(p_imethod.method_doc->description), &p_itype);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- Vector<String> description_lines = p_imethod.method_doc->description.split("\n");
+ if (summary_lines.size() || default_args_doc.get_string_length()) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
- for (int i = 0; i < description_lines.size(); i++) {
- String description_line = description_lines[i].strip_edges();
- if (description_line.size()) {
- p_output.push_back(INDENT2 "/// ");
- p_output.push_back(description_line.xml_escape());
- p_output.push_back("\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
}
- }
- for (List<String>::Element *E = default_args_doc.front(); E; E = E->next()) {
- p_output.push_back(E->get().xml_escape());
+ p_output.append(default_args_doc.as_string());
+ p_output.append(INDENT2 "/// </summary>");
}
-
- p_output.push_back(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
@@ -1149,16 +1665,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
}
@@ -1172,37 +1688,37 @@ 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;
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
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);
+ ERR_FAIL_COND_V_MSG(!dir_exists, ERR_FILE_BAD_PATH, "The output directory does not exist.");
- 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();
@@ -1222,17 +1738,15 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls;
- OS::get_singleton()->print(String("Generating " + itype.name + "...\n").utf8());
+ OS::get_singleton()->print("Generating %s...\n", itype.name.utf8().get_data());
String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types
for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) {
const MethodInterface &imethod = E->get();
Error method_err = _generate_glue_method(itype, imethod, output);
- if (method_err != OK) {
- ERR_EXPLAIN("Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'");
- ERR_FAIL_V(method_err);
- }
+ ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
+ "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");
}
if (itype.is_singleton) {
@@ -1242,11 +1756,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) {
@@ -1255,43 +1769,46 @@ 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.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("uint64_t get_core_api_hash() { return ");
- output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "; }\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("#ifdef TOOLS_ENABLED\n"
- "uint64_t get_editor_api_hash() { return ");
- output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) +
- "; }\n#endif // TOOLS_ENABLED\n");
+ output.append("uint32_t get_bindings_version() { return ");
+ output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\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_cs_glue_version() { return ");
+ output.append(String::num_uint64(CS_GLUE_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;
@@ -1300,11 +1817,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;
}
}
@@ -1314,23 +1831,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;
}
}
@@ -1340,16 +1857,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;
@@ -1362,23 +1879,19 @@ 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());
- }
+ ERR_FAIL_COND_V_MSG(!file, ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'.");
+ 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
@@ -1434,15 +1947,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 + "()");
@@ -1450,6 +1963,15 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
String ptrcall_return_type;
String initialization;
+ if (p_imethod.is_vararg && return_type->cname != name_cache.type_Variant) {
+ // VarArg methods always return Variant, but there are some cases in which MethodInfo provides
+ // a specific return type. We trust this information is valid. We need a temporary local to keep
+ // 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.append("\tVariant " C_LOCAL_VARARG_RET ";\n");
+ }
+
if (return_type->is_object_type) {
ptrcall_return_type = return_type->is_reference ? "Ref<Reference>" : return_type->c_type;
initialization = return_type->is_reference ? "" : " = NULL";
@@ -1457,79 +1979,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("\tVector<Variant> varargs;\n"
- "\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\t");
- p_output.push_back(err_fail_macro);
- p_output.push_back("(varargs.resize(vararg_length) != OK");
- p_output.push_back(fail_ret);
- p_output.push_back(");\n\tVector<Variant*> " C_LOCAL_PTRCALL_ARGS ";\n\t");
- p_output.push_back(err_fail_macro);
- p_output.push_back("(call_args.resize(total_length) != OK");
- p_output.push_back(fail_ret);
- p_output.push_back(");\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.write[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)
- p_output.push_back(C_LOCAL_RET " = ");
+ if (!ret_void) {
+ // See the comment on the C_LOCAL_VARARG_RET declaration
+ if (return_type->cname != name_cache.type_Variant) {
+ p_output.append(C_LOCAL_VARARG_RET " = ");
+ } else {
+ p_output.append(C_LOCAL_RET " = ");
+ }
+ }
+
+ 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");
- p_output.push_back(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", ");
- p_output.push_back(p_imethod.arguments.size() ? "(const Variant**)" C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL");
- p_output.push_back(", 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.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;
@@ -1569,7 +2094,7 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol
if (found)
return found;
- ERR_PRINTS(String() + "Type not found. Creating placeholder: " + p_typeref.cname.operator String());
+ ERR_PRINTS(String() + "Type not found. Creating placeholder: '" + p_typeref.cname.operator String() + "'.");
const Map<StringName, TypeInterface>::Element *match = placeholder_types.find(p_typeref.cname);
@@ -1582,7 +2107,59 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol
return &placeholder_types.insert(placeholder.cname, placeholder)->get();
}
-void BindingsGenerator::_populate_object_type_interfaces() {
+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
+ }
+}
+
+bool BindingsGenerator::_populate_object_type_interfaces() {
obj_types.clear();
@@ -1601,15 +2178,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;
}
@@ -1620,7 +2195,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;
@@ -1637,10 +2212,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();
@@ -1650,24 +2227,30 @@ void BindingsGenerator::_populate_object_type_interfaces() {
PropertyInterface iprop;
iprop.cname = property.name;
- iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
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);
+ ERR_FAIL_COND_V(!valid, false);
- // Prevent property and enclosing type from sharing the same name
+ iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
+
+ // 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 += "_";
}
+ iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash...
+
iprop.prop_doc = NULL;
for (int i = 0; i < itype.class_doc->properties.size(); i++) {
@@ -1699,9 +2282,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;
@@ -1713,35 +2301,39 @@ void BindingsGenerator::_populate_object_type_interfaces() {
imethod.is_vararg = m && m->is_vararg();
if (!m && !imethod.is_virtual) {
- if (virtual_method_list.find(method_info)) {
- // A virtual method without the virtual flag. This is a special case.
-
- // There is no method bind, so let's fallback to Godot's object.Call(string, params)
- imethod.requires_object_call = true;
-
- // The method Object.free is registered as a virtual method, but without the virtual flag.
- // This is because this method is not supposed to be overridden, but called.
- // We assume the return type is void.
- imethod.return_type.cname = name_cache.type_void;
-
- // Actually, more methods like this may be added in the future,
- // which could actually will return something different.
- // Let's put this to notify us if that ever happens.
- 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);
- }
- }
- } else {
- ERR_PRINTS("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name);
+ ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,
+ "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");
+
+ // A virtual method without the virtual flag. This is a special case.
+
+ // There is no method bind, so let's fallback to Godot's object.Call(string, params)
+ imethod.requires_object_call = true;
+
+ // The method Object.free is registered as a virtual method, but without the virtual flag.
+ // This is because this method is not supposed to be overridden, but called.
+ // We assume the return type is void.
+ imethod.return_type.cname = name_cache.type_void;
+
+ // Actually, more methods like this may be added in the future,
+ // which could actually will return something different.
+ // Let's put this to notify us if that ever happens.
+ if (itype.cname != name_cache.type_Object || imethod.name != "free") {
+ WARN_PRINTS("Notification: New unexpected virtual non-overridable method found."
+ " We only expected Object.free, but found '" +
+ itype.name + "." + imethod.name + "'.");
}
} else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
imethod.return_type.cname = return_info.class_name;
imethod.return_type.is_enum = true;
} else if (return_info.class_name != StringName()) {
imethod.return_type.cname = return_info.class_name;
+ if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) {
+ /* clang-format off */
+ ERR_PRINTS("Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'."
+ " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");
+ /* clang-format on */
+ ERR_FAIL_V(false);
+ }
} 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) {
@@ -1749,14 +2341,22 @@ 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++) {
PropertyInfo arginfo = method_info.arguments[i];
+ String orig_arg_name = arginfo.name;
+
ArgumentInterface iarg;
- iarg.name = arginfo.name;
+ iarg.name = orig_arg_name;
if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
iarg.type.cname = arginfo.class_name;
@@ -1768,13 +2368,21 @@ 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));
if (m && m->has_default_argument(i)) {
- _default_argument_from_variant(m->get_default_argument(i), iarg);
+ bool defval_ok = _arg_default_value_from_variant(m->get_default_argument(i), iarg);
+ ERR_FAIL_COND_V_MSG(!defval_ok, false,
+ "Cannot determine default value for argument '" + orig_arg_name + "' of method '" + itype.name + "." + imethod.name + "'.");
}
imethod.add_argument(iarg);
@@ -1789,16 +2397,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) {
@@ -1809,8 +2425,8 @@ void BindingsGenerator::_populate_object_type_interfaces() {
}
if (!imethod.is_virtual && imethod.name[0] == '_') {
- for (const List<PropertyInterface>::Element *E = itype.properties.front(); E; E = E->next()) {
- const PropertyInterface &iprop = E->get();
+ for (const List<PropertyInterface>::Element *F = itype.properties.front(); F; F = F->next()) {
+ const PropertyInterface &iprop = F->get();
if (iprop.setter == imethod.name || iprop.getter == imethod.name) {
imethod.is_internal = true;
@@ -1825,8 +2441,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;
@@ -1841,13 +2457,15 @@ 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()) {
- int *value = class_info->constant_map.getptr(E->get());
- ERR_FAIL_NULL(value);
- constant_list.erase(E->get().operator String());
+ 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_V(value, false);
+ constants.erase(constant_name);
- ConstantInterface iconstant(snake_to_pascal_case(E->get(), true), *value);
+ ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
iconstant.const_doc = NULL;
for (int i = 0; i < itype.class_doc->constants.size(); i++) {
@@ -1862,7 +2480,9 @@ void BindingsGenerator::_populate_object_type_interfaces() {
ienum.constants.push_back(iconstant);
}
- ienum.prefix = _determine_enum_prefix(ienum);
+ int prefix_length = _determine_enum_prefix(ienum);
+
+ _apply_prefix_to_enum_constants(ienum, prefix_length);
itype.enums.push_back(ienum);
@@ -1875,11 +2495,12 @@ 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()) {
- int *value = class_info->constant_map.getptr(E->get());
- ERR_FAIL_NULL(value);
+ 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_V(value, false);
- ConstantInterface iconstant(snake_to_pascal_case(E->get(), true), *value);
+ ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
iconstant.const_doc = NULL;
for (int i = 0; i < itype.class_doc->constants.size(); i++) {
@@ -1898,21 +2519,18 @@ void BindingsGenerator::_populate_object_type_interfaces() {
class_list.pop_front();
}
+
+ return true;
}
-void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
+bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
r_iarg.default_argument = p_val;
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:
@@ -1951,15 +2569,24 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::OBJECT:
- if (p_val.is_zero()) {
- r_iarg.default_argument = "null";
- break;
- }
+ ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
+
+ r_iarg.default_argument = "null";
+ break;
case Variant::DICTIONARY:
- case Variant::_RID:
r_iarg.default_argument = "new %s()";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
break;
+ case Variant::_RID:
+ ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");
+
+ ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
+
+ r_iarg.default_argument = "null";
+ break;
case Variant::ARRAY:
case Variant::POOL_BYTE_ARRAY:
case Variant::POOL_INT_ARRAY:
@@ -1977,11 +2604,14 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg
r_iarg.default_argument = Variant::get_type_name(p_val.get_type()) + ".Identity";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
- default: {}
+ default: {
+ }
}
if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null")
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
+
+ return true;
}
void BindingsGenerator::_populate_builtin_type_interfaces() {
@@ -1990,18 +2620,18 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
TypeInterface itype;
-#define INSERT_STRUCT_TYPE(m_type, m_type_in) \
- { \
- itype = TypeInterface::create_value_type(String(#m_type)); \
- itype.c_in = "\tMARSHALLED_IN(" #m_type ", %1, %1_in);\n"; \
- itype.c_out = "\tMARSHALLED_OUT(" #m_type ", %1, ret_out)\n" \
- "\treturn mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(%2), ret_out);\n"; \
- itype.c_arg_in = "&%s_in"; \
- itype.c_type_in = m_type_in; \
- itype.cs_in = "ref %s"; \
- itype.cs_out = "return (%1)%0;"; \
- itype.im_type_out = "object"; \
- builtin_types.insert(itype.cname, itype); \
+#define INSERT_STRUCT_TYPE(m_type, m_type_in) \
+ { \
+ itype = TypeInterface::create_value_type(String(#m_type)); \
+ itype.c_in = "\t%0 %1_in = MARSHALLED_IN(" #m_type ", %1);\n"; \
+ itype.c_out = "\treturn MARSHALLED_OUT(" #m_type ", %1);\n"; \
+ itype.c_arg_in = "&%s_in"; \
+ itype.c_type_in = "GDMonoMarshal::M_" #m_type "*"; \
+ itype.c_type_out = "GDMonoMarshal::M_" #m_type; \
+ itype.cs_in = "ref %s"; \
+ itype.cs_out = "return (%1)%0;"; \
+ itype.im_type_out = itype.cs_type; \
+ builtin_types.insert(itype.cname, itype); \
}
INSERT_STRUCT_TYPE(Vector2, "real_t*")
@@ -2019,53 +2649,86 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
// bool
itype = TypeInterface::create_value_type(String("bool"));
- itype.c_arg_in = "&%s";
- // /* MonoBoolean <---> bool
- itype.c_in = "\t%0 %1_in = (%0)%1;\n";
- itype.c_out = "\treturn (%0)%1;\n";
- itype.c_type = "bool";
- // */
- itype.c_type_in = "MonoBoolean";
- itype.c_type_out = itype.c_type_in;
+ {
+ // MonoBoolean <---> bool
+ itype.c_in = "\t%0 %1_in = (%0)%1;\n";
+ itype.c_out = "\treturn (%0)%1;\n";
+ itype.c_type = "bool";
+ itype.c_type_in = "MonoBoolean";
+ itype.c_type_out = itype.c_type_in;
+ itype.c_arg_in = "&%s_in";
+ }
itype.im_type_in = itype.name;
itype.im_type_out = itype.name;
builtin_types.insert(itype.cname, itype);
- // int
- itype = TypeInterface::create_value_type(String("int"));
- itype.c_arg_in = "&%s_in";
- // /* ptrcall only supports int64_t and uint64_t
- itype.c_in = "\t%0 %1_in = (%0)%1;\n";
- itype.c_out = "\treturn (%0)%1;\n";
- itype.c_type = "int64_t";
- // */
- 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);
+ // Integer types
+ {
+ // 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); \
+ }
- // real_t
- itype = TypeInterface();
-#ifdef REAL_T_IS_DOUBLE
- itype.name = "double";
-#else
- itype.name = "float";
-#endif
- itype.cname = itype.name;
- itype.proxy_name = itype.name;
- itype.c_arg_in = "&%s_in";
- //* ptrcall only supports 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 = "real_t";
- itype.c_type_out = "real_t";
- 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);
+ // 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
+ {
+ // 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 = "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);
+ }
// String
itype = TypeInterface();
@@ -2234,8 +2897,7 @@ void BindingsGenerator::_populate_global_constants() {
if (global_constants_count > 0) {
Map<String, DocData::ClassDoc>::Element *match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope");
- ERR_EXPLAIN("Could not find `@GlobalScope` in DocData");
- CRASH_COND(!match);
+ CRASH_COND_MSG(!match, "Could not find '@GlobalScope' in DocData.");
const DocData::ClassDoc &global_scope_doc = match->value();
@@ -2244,8 +2906,8 @@ void BindingsGenerator::_populate_global_constants() {
String constant_name = GlobalConstants::get_global_constant_name(i);
const DocData::ConstantDoc *const_doc = NULL;
- for (int i = 0; i < global_scope_doc.constants.size(); i++) {
- const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[i];
+ for (int j = 0; j < global_scope_doc.constants.size(); j++) {
+ const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j];
if (curr_const_doc.name == constant_name) {
const_doc = &curr_const_doc;
@@ -2256,14 +2918,14 @@ void BindingsGenerator::_populate_global_constants() {
int constant_value = GlobalConstants::get_global_constant_value(i);
StringName enum_name = GlobalConstants::get_global_constant_enum(i);
- ConstantInterface iconstant(snake_to_pascal_case(constant_name, true), constant_value);
+ ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value);
iconstant.const_doc = const_doc;
if (enum_name != StringName()) {
EnumInterface ienum(enum_name);
- List<EnumInterface>::Element *match = global_enums.find(ienum);
- if (match) {
- match->get().constants.push_back(iconstant);
+ List<EnumInterface>::Element *enum_match = global_enums.find(ienum);
+ if (enum_match) {
+ enum_match->get().constants.push_back(iconstant);
} else {
ienum.constants.push_back(iconstant);
global_enums.push_back(ienum);
@@ -2284,16 +2946,18 @@ void BindingsGenerator::_populate_global_constants() {
TypeInterface::postsetup_enum_type(enum_itype);
enum_types.insert(enum_itype.cname, enum_itype);
- ienum.prefix = _determine_enum_prefix(ienum);
+ int prefix_length = _determine_enum_prefix(ienum);
- // HARDCODED
+ // HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'.
if (ienum.cname == name_cache.enum_Error) {
- if (!ienum.prefix.empty()) { // Just in case it ever changes
- ERR_PRINTS("Prefix for enum 'Error' is not empty");
+ if (prefix_length > 0) { // Just in case it ever changes
+ ERR_PRINTS("Prefix for enum '" _STR(Error) "' is not empty.");
}
- ienum.prefix = "Err";
+ prefix_length = 1; // 'ERR_'
}
+
+ _apply_prefix_to_enum_constants(ienum, prefix_length);
}
}
@@ -2313,13 +2977,37 @@ 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() {
+
+ initialized = false;
EditorHelp::generate_doc();
enum_types.clear();
- _populate_object_type_interfaces();
+ _initialize_blacklisted_methods();
+
+ bool obj_type_ok = _populate_object_type_interfaces();
+ ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");
+
_populate_builtin_type_interfaces();
_populate_global_constants();
@@ -2331,65 +3019,56 @@ void BindingsGenerator::initialize() {
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next())
_generate_method_icalls(E.get());
+
+ initialized = true;
}
void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
- const int NUM_OPTIONS = 3;
- int options_left = NUM_OPTIONS;
+ const int NUM_OPTIONS = 2;
+ String generate_all_glue_option = "--generate-mono-glue";
+ String generate_cs_glue_option = "--generate-mono-cs-glue";
+ String generate_cpp_glue_option = "--generate-mono-cpp-glue";
- String mono_glue_option = "--generate-mono-glue";
- String cs_core_api_option = "--generate-cs-core-api";
- String cs_editor_api_option = "--generate-cs-editor-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_PRINT("Mono glue generation failed");
+ glue_dir_path = path_elem->get();
elem = elem->next();
} else {
- ERR_PRINTS("--generate-mono-glue: 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() == cs_core_api_option) {
-
+ } else if (elem->get() == generate_cs_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
- if (get_singleton()->generate_cs_core_project(path_elem->get()) != OK)
- ERR_PRINT("Generation of solution and C# project for the Core API failed");
+ cs_dir_path = path_elem->get();
elem = elem->next();
} else {
- ERR_PRINTS(cs_core_api_option + ": No output directory specified");
+ ERR_PRINTS(generate_cs_glue_option + ": No output directory specified.");
}
--options_left;
-
- } else if (elem->get() == cs_editor_api_option) {
-
+ } else if (elem->get() == generate_cpp_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
- if (path_elem->next()) {
- if (get_singleton()->generate_cs_editor_project(path_elem->get(), path_elem->next()->get()) != OK)
- ERR_PRINT("Generation of solution and C# project for the Editor API failed");
- elem = path_elem->next();
- } else {
- ERR_PRINTS(cs_editor_api_option + ": No hint path for the Core API dll specified");
- }
+ cpp_dir_path = path_elem->get();
+ elem = elem->next();
} else {
- ERR_PRINTS(cs_editor_api_option + ": No output directory specified");
+ ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified.");
}
--options_left;
@@ -2398,10 +3077,36 @@ 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 (!bindings_generator.initialized) {
+ ERR_PRINTS("Failed to initialize the bindings generator");
+ ::exit(0);
+ }
+
+ if (glue_dir_path.length()) {
+ if (bindings_generator.generate_glue(glue_dir_path) != OK)
+ ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue.");
+
+ if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK)
+ ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API.");
+ }
- if (options_left != NUM_OPTIONS)
- exit(0);
+ 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 ad89255ba5..8f3676940b 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -32,10 +32,11 @@
#define BINDINGS_GENERATOR_H
#include "core/class_db.h"
+#include "core/string_builder.h"
#include "editor/doc/doc_data.h"
#include "editor/editor_help.h"
-#ifdef DEBUG_METHODS_ENABLED
+#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
#include "core/ustring.h"
@@ -43,20 +44,21 @@ class BindingsGenerator {
struct ConstantInterface {
String name;
+ String proxy_name;
int value;
const DocData::ConstantDoc *const_doc;
ConstantInterface() {}
- ConstantInterface(const String &p_name, int p_value) {
+ ConstantInterface(const String &p_name, const String &p_proxy_name, int p_value) {
name = p_name;
+ proxy_name = p_proxy_name;
value = p_value;
}
};
struct EnumInterface {
StringName cname;
- String prefix;
List<ConstantInterface> constants;
_FORCE_INLINE_ bool operator==(const EnumInterface &p_ienum) const {
@@ -85,8 +87,13 @@ class BindingsGenerator {
StringName cname;
bool is_enum;
- TypeReference() {
- is_enum = false;
+ TypeReference() :
+ is_enum(false) {
+ }
+
+ TypeReference(const StringName &p_cname) :
+ cname(p_cname),
+ is_enum(false) {
}
};
@@ -140,7 +147,7 @@ class BindingsGenerator {
bool requires_object_call;
/**
- * Determines if the method visibility is `internal` (visible only to files in the same assembly).
+ * Determines if the method visibility is 'internal' (visible only to files in the same assembly).
* Currently, we only use this for methods that are not meant to be exposed,
* but are required by properties as getters or setters.
* Methods that are not meant to be exposed are those that begin with underscore and are not virtual.
@@ -151,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;
}
};
@@ -223,7 +233,7 @@ class BindingsGenerator {
String c_in;
/**
- * Determines the name of the variable that will be passed as argument to a ptrcall.
+ * Determines the expression that will be passed as argument to ptrcall.
* By default the value equals the name of the parameter,
* this varies for types that require special manipulation via [c_in].
* Formatting elements:
@@ -319,6 +329,15 @@ class BindingsGenerator {
return NULL;
}
+ const PropertyInterface *find_property_by_name(const StringName &p_cname) const {
+ for (const List<PropertyInterface>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().cname == p_cname)
+ return &E->get();
+ }
+
+ return NULL;
+ }
+
const PropertyInterface *find_property_by_proxy_name(const String &p_proxy_name) const {
for (const List<PropertyInterface>::Element *E = properties.front(); E; E = E->next()) {
if (E->get().proxy_name == p_proxy_name)
@@ -333,8 +352,6 @@ class BindingsGenerator {
itype.proxy_name = itype.name;
itype.c_type = itype.name;
- itype.c_type_in = "void*";
- itype.c_type_out = "MonoObject*";
itype.cs_type = itype.proxy_name;
itype.im_type_in = "ref " + itype.proxy_name;
itype.im_type_out = itype.proxy_name;
@@ -385,10 +402,19 @@ class BindingsGenerator {
}
static void postsetup_enum_type(TypeInterface &r_enum_itype) {
- r_enum_itype.c_arg_in = "&%s";
- r_enum_itype.c_type = "int";
- r_enum_itype.c_type_in = "int";
- r_enum_itype.c_type_out = "int";
+ // 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";
+ {
+ // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'.
+ r_enum_itype.c_in = "\t%0 %1_in = (%0)%1;\n";
+ r_enum_itype.c_out = "\treturn (%0)%1;\n";
+ r_enum_itype.c_type = "int64_t";
+ }
+ r_enum_itype.c_type_in = "int32_t";
+ r_enum_itype.c_type_out = r_enum_itype.c_type_in;
+
r_enum_itype.cs_type = r_enum_itype.proxy_name;
r_enum_itype.cs_in = "(int)%s";
r_enum_itype.cs_out = "return (%1)%0;";
@@ -445,7 +471,8 @@ class BindingsGenerator {
}
};
- static bool verbose_output;
+ bool log_print_enabled;
+ bool initialized;
OrderedHashMap<StringName, TypeInterface> obj_types;
@@ -464,29 +491,60 @@ 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;
StringName type_VarArg;
StringName type_Object;
StringName type_Reference;
+ StringName type_RID;
+ StringName type_String;
+ StringName type_at_GlobalScope;
StringName enum_Error;
+ StringName type_sbyte;
+ StringName type_short;
+ StringName type_int;
+ StringName type_long;
+ StringName type_byte;
+ StringName type_ushort;
+ StringName type_uint;
+ StringName type_ulong;
+ StringName type_float;
+ StringName type_double;
+
NameCache() {
type_void = StaticCString::create("void");
- type_int = StaticCString::create("int");
type_Array = StaticCString::create("Array");
type_Dictionary = StaticCString::create("Dictionary");
type_Variant = StaticCString::create("Variant");
type_VarArg = StaticCString::create("VarArg");
type_Object = StaticCString::create("Object");
type_Reference = StaticCString::create("Reference");
+ type_RID = StaticCString::create("RID");
+ type_String = StaticCString::create("String");
+ type_at_GlobalScope = StaticCString::create("@GlobalScope");
enum_Error = StaticCString::create("Error");
+
+ type_sbyte = StaticCString::create("sbyte");
+ type_short = StaticCString::create("short");
+ type_int = StaticCString::create("int");
+ type_long = StaticCString::create("long");
+ type_byte = StaticCString::create("byte");
+ type_ushort = StaticCString::create("ushort");
+ type_uint = StaticCString::create("uint");
+ type_ulong = StaticCString::create("ulong");
+ type_float = StaticCString::create("float");
+ type_double = StaticCString::create("double");
}
+ private:
NameCache(const NameCache &);
NameCache &operator=(const NameCache &);
};
@@ -502,6 +560,15 @@ class BindingsGenerator {
return NULL;
}
+ const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const {
+ for (const List<ConstantInterface>::Element *E = p_constants.front(); E; E = E->next()) {
+ if (E->get().name == p_name)
+ return &E->get();
+ }
+
+ return NULL;
+ }
+
inline String get_unique_sig(const TypeInterface &p_type) {
if (p_type.is_reference)
return "Ref";
@@ -513,57 +580,61 @@ class BindingsGenerator {
return p_type.name;
}
- String _determine_enum_prefix(const EnumInterface &p_ienum);
+ String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype);
+
+ int _determine_enum_prefix(const EnumInterface &p_ienum);
+ void _apply_prefix_to_enum_constants(EnumInterface &p_ienum, int p_prefix_length);
void _generate_method_icalls(const TypeInterface &p_itype);
const TypeInterface *_get_type_or_null(const TypeReference &p_typeref);
const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref);
- void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
+ StringName _get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
+ StringName _get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
+
+ bool _arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
- void _populate_object_type_interfaces();
+ bool _populate_object_type_interfaces();
void _populate_builtin_type_interfaces();
void _populate_global_constants();
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);
-
- void _generate_global_constants(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);
- Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, List<String> &p_output);
+ void _generate_global_constants(StringBuilder &p_output);
- Error _save_file(const String &p_path, const List<String> &p_content);
+ Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output);
- BindingsGenerator() {}
+ Error _save_file(const String &p_path, const StringBuilder &p_content);
- BindingsGenerator(const BindingsGenerator &);
- BindingsGenerator &operator=(const BindingsGenerator &);
+ void _log(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
- friend class CSharpLanguage;
- static BindingsGenerator *singleton;
+ void _initialize();
public:
- Error generate_cs_core_project(const String &p_output_dir, bool p_verbose_output = true);
- Error generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, 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);
- static uint32_t get_version();
+ _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; }
- void initialize();
+ _FORCE_INLINE_ bool is_initialized() { return initialized; }
- _FORCE_INLINE_ static BindingsGenerator *get_singleton() {
- if (!singleton) {
- singleton = memnew(BindingsGenerator);
- singleton->initialize();
- }
- return singleton;
- }
+ static uint32_t get_version();
static void handle_cmdline_args(const List<String> &p_cmdline_args);
+
+ BindingsGenerator() :
+ log_print_enabled(true),
+ initialized(false) {
+ _initialize();
+ }
};
#endif
diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp
index 4764cbe941..0e6c58c9d7 100644
--- a/modules/mono/editor/csharp_project.cpp
+++ b/modules/mono/editor/csharp_project.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -30,81 +30,76 @@
#include "csharp_project.h"
+#include "core/io/json.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/project_settings.h"
+#include "../csharp_script.h"
#include "../mono_gd/gd_mono_class.h"
#include "../mono_gd/gd_mono_marshal.h"
+#include "../utils/string_utils.h"
+#include "script_class_parser.h"
namespace CSharpProject {
-String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) {
+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) {
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
+ GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator");
- 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 };
+ 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_unhandled_exception(exc);
- ERR_FAIL_V(String());
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ 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_dll_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_dll_path = p_core_dll_path;
- Variant compile_items = p_files;
- const Variant *args[3] = { &dir, &core_dll_path, &compile_items };
- MonoException *exc = NULL;
- MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc);
+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) {
- if (exc) {
- GDMonoUtils::debug_unhandled_exception(exc);
- ERR_FAIL_V(String());
- }
+ 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);
- return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
-}
+ _GDMONO_SCOPE_DOMAIN_(temp_domain);
-String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) {
+ GDMonoAssembly *tools_project_editor_asm = NULL;
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
+ bool assembly_loaded = GDMono::get_singleton()->load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_asm);
+ ERR_FAIL_COND_V_MSG(!assembly_loaded, false, "Failed to load assembly: '" TOOLS_PROJECT_EDITOR_ASM_NAME "'.");
- GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
-
- 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 (exc) {
- GDMonoUtils::debug_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_asm);
}
-
- 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) {
- _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
+ if (!GLOBAL_DEF("mono/project/auto_update_project", true))
+ return;
+
+ GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
- GDMonoClass *klass = 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;
@@ -114,8 +109,9 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
klass->get_method("AddItemToProjectChecked", 3)->invoke(NULL, args, &exc);
if (exc) {
- GDMonoUtils::debug_unhandled_exception(exc);
+ GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL();
}
}
+
} // namespace CSharpProject
diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h
index d852139de0..b42762cea2 100644
--- a/modules/mono/editor/csharp_project.h
+++ b/modules/mono/editor/csharp_project.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -35,11 +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);
+
} // namespace CSharpProject
#endif // CSHARP_PROJECT_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..5a84d9e3b8
--- /dev/null
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -0,0 +1,445 @@
+/*************************************************************************/
+/* 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/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_Globals_EditorScale() {
+ return EDSCALE;
+}
+
+MonoObject *godot_icall_Globals_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_Globals_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 = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
+ return GDMonoMarshal::variant_to_mono_object(result);
+}
+
+MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
+ String text = GDMonoMarshal::mono_string_to_godot(p_text);
+ return GDMonoMarshal::mono_string_from_godot(TTR(text));
+}
+
+MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt() {
+ String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt();
+ return GDMonoMarshal::mono_string_from_godot(error_str);
+}
+
+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(bundle_id);
+#else
+ (void)p_bundle_id; // UNUSED
+ return (MonoBoolean) false;
+#endif
+}
+
+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
+}
+
+void godot_icall_Internal_EditorRunPlay() {
+ EditorNode::get_singleton()->run_play();
+}
+
+void godot_icall_Internal_EditorRunStop() {
+ EditorNode::get_singleton()->run_stop();
+}
+
+void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
+ ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger();
+ if (sed) {
+ sed->reload_scripts();
+ }
+}
+
+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_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts);
+
+ // Globals
+ mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale);
+ mono_add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", (void *)godot_icall_Globals_GlobalDef);
+ mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", (void *)godot_icall_Globals_EditorDef);
+ mono_add_internal_call("GodotTools.Internals.Globals::internal_TTR", (void *)godot_icall_Globals_TTR);
+
+ // Utils.OS
+ mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
+}
diff --git a/modules/mono/editor/editor_internal_calls.h b/modules/mono/editor/editor_internal_calls.h
new file mode 100644
index 0000000000..1682da66e5
--- /dev/null
+++ b/modules/mono/editor/editor_internal_calls.h
@@ -0,0 +1,36 @@
+/*************************************************************************/
+/* editor_internal_calls.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 EDITOR_INTERNAL_CALL_H
+#define EDITOR_INTERNAL_CALL_H
+
+void register_editor_internal_calls();
+
+#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 d397814fa7..0000000000
--- a/modules/mono/editor/godotsharp_builds.cpp
+++ /dev/null
@@ -1,600 +0,0 @@
-/*************************************************************************/
-/* godotsharp_builds.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 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 "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.length()) {
- if (!msbuild_tools_path.ends_with("\\"))
- msbuild_tools_path += "\\";
-
- return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe");
- }
-
- print_verbose("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Trying with '" PROP_NAME_MSBUILD_MONO "'...");
- } // FALL THROUGH
- 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)) {
- WARN_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path);
- }
-
- 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)) {
- WARN_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path);
- }
-
- 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()) {
- WARN_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()) {
- WARN_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_FrameworkPath() {
-
-#if defined(WINDOWS_ENABLED)
- const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info();
- if (mono_reg_info.assembly_dir.length()) {
- String framework_path = path_join(mono_reg_info.assembly_dir, "mono", "4.5");
- return GDMonoMarshal::mono_string_from_godot(framework_path);
- }
-
- ERR_EXPLAIN("Cannot find Mono's assemblies directory in the registry");
- ERR_FAIL_V(NULL);
-#else
- return 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
-}
-
-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_FrameworkPath", (void *)godot_icall_BuildInstance_get_FrameworkPath);
- 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);
-}
-
-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_name, const String &p_api_sln_dir, const String &p_config) {
-
- String api_sln_file = p_api_sln_dir.plus_file(p_name + ".sln");
- String api_assembly_dir = p_api_sln_dir.plus_file("bin").plus_file(p_config);
- String api_assembly_file = api_assembly_dir.plus_file(p_name + ".dll");
-
- if (!FileAccess::exists(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 " + p_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) {
-
- 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)) {
- DirAccess *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);
-
- memdelete(da);
-
- 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_sln(APIAssembly::Type p_api_type) {
-
- String api_name = p_api_type == APIAssembly::API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
- String api_build_config = "Release";
-
- EditorProgress pr("mono_build_release_" + api_name, "Building " + api_name + " solution...", 4);
-
- pr.step("Generating " + api_name + " solution");
-
- String core_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
- .plus_file(_api_folder_name(APIAssembly::API_CORE))
- .plus_file(API_ASSEMBLY_NAME);
- String editor_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
- .plus_file(_api_folder_name(APIAssembly::API_EDITOR))
- .plus_file(EDITOR_API_ASSEMBLY_NAME);
-
- String api_sln_dir = p_api_type == APIAssembly::API_CORE ? core_api_sln_dir : editor_api_sln_dir;
- String api_sln_file = api_sln_dir.plus_file(api_name + ".sln");
-
- if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) {
- String core_api_assembly;
-
- if (p_api_type == APIAssembly::API_EDITOR) {
- core_api_assembly = core_api_sln_dir.plus_file("bin")
- .plus_file(api_build_config)
- .plus_file(API_ASSEMBLY_NAME ".dll");
- }
-
-#ifndef DEBUG_METHODS_ENABLED
-#error "How am I supposed to generate the bindings?"
-#endif
-
- BindingsGenerator *gen = BindingsGenerator::get_singleton();
- bool gen_verbose = OS::get_singleton()->is_stdout_verbose();
-
- Error err = p_api_type == APIAssembly::API_CORE ?
- gen->generate_cs_core_project(api_sln_dir, gen_verbose) :
- gen->generate_cs_editor_project(api_sln_dir, core_api_assembly, gen_verbose);
-
- if (err != OK) {
- show_build_error_dialog("Failed to generate " + api_name + " solution. Error: " + itos(err));
- return false;
- }
- }
-
- pr.step("Building " + api_name + " solution");
-
- if (!GodotSharpBuilds::build_api_sln(api_name, api_sln_dir, api_build_config))
- return false;
-
- pr.step("Copying " + api_name + " assembly");
-
- String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
-
- // Create assemblies directory if needed
- if (!DirAccess::exists(res_assemblies_dir)) {
- DirAccess *da = DirAccess::create_for_path(res_assemblies_dir);
- Error err = da->make_dir_recursive(res_assemblies_dir);
- memdelete(da);
-
- if (err != OK) {
- show_build_error_dialog("Failed to create assemblies directory. Error: " + itos(err));
- return false;
- }
- }
-
- // Copy the built assembly to the assemblies directory
- String api_assembly_dir = api_sln_dir.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;
-
- pr.step("Done");
-
- 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_sln(APIAssembly::API_CORE))
- return false;
-
- if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR))
- return false;
-
- EditorProgress pr("mono_project_debug_build", "Building project solution...", 2);
-
- pr.step("Building project solution");
-
- 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;
- }
-
- pr.step("Done");
-
- return true;
-}
-
-bool GodotSharpBuilds::editor_build_callback() {
-
- 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();
-
- EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO);
-
- 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));
-}
-
-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 = "msbuild_issues.csv";
- 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) {
- print_verbose("MSBuild finished with exit code " + itos(exit_code));
- }
-
- 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 c6dc6b6236..0000000000
--- a/modules/mono/editor/godotsharp_builds.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/*************************************************************************/
-/* godotsharp_builds.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 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);
-
- 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_name, 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_sln(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 3ee38515bf..0000000000
--- a/modules/mono/editor/godotsharp_editor.cpp
+++ /dev/null
@@ -1,482 +0,0 @@
-/*************************************************************************/
-/* godotsharp_editor.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 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/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 "godotsharp_export.h"
-#include "net_solution.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()) {
-
- NETSolution solution(name);
-
- if (!solution.set_path(path)) {
- show_error_dialog(TTR("Failed to create solution."));
- return false;
- }
-
- Vector<String> extra_configs;
- extra_configs.push_back("Tools");
-
- solution.add_new_project(name, guid, extra_configs);
-
- Error sln_error = solution.save();
-
- if (sln_error != OK) {
- show_error_dialog(TTR("Failed to save solution."));
- return false;
- }
-
- if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE))
- return false;
-
- if (!GodotSharpBuilds::make_api_sln(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::_remove_create_sln_menu_option() {
-
- menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN));
-
- if (menu_popup->get_item_count() == 0)
- menu_button->hide();
-
- 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::_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("_create_project_solution"), &GodotSharpEditor::_create_project_solution);
- 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();
-}
-
-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
- vscode_path = path_which("code");
- }
-
- 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_button = memnew(MenuButton);
- menu_button->set_text(TTR("Mono"));
- menu_popup = menu_button->get_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 a brand new feature and a work in progress.\n") +
- "It is currently in an alpha stage and is not suitable for use in production.\n\n" +
- "As of Godot 3.1, C# support is not feature-complete and may crash in some situations. " +
- "Bugs and usability issues will be addressed gradually over future 3.x releases, " +
- "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, Mono 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)) {
- bottom_panel_btn->hide();
- menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN);
- }
-
- menu_popup->connect("id_pressed", this, "_menu_option_pressed");
-
- if (menu_popup->get_item_count() == 0)
- menu_button->hide();
-
- editor->get_menu_hb()->add_child(menu_button);
-
- // External editor settings
- EditorSettings *ed_settings = EditorSettings::get_singleton();
- EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE);
-
- String settings_hint_str = "None";
-
-#ifdef WINDOWS_ENABLED
- settings_hint_str += ",MonoDevelop,Visual Studio Code";
-#elif OSX_ENABLED
- settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code";
-#elif 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() {
-
- CSharpLanguage::get_singleton()->reload_assemblies_if_needed(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();
- CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true);
- } 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 46b6bd5ebf..0000000000
--- a/modules/mono/editor/godotsharp_editor.h
+++ /dev/null
@@ -1,130 +0,0 @@
-/*************************************************************************/
-/* godotsharp_editor.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 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 _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);
-
- 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,
-#ifdef WINDOWS_ENABLED
- //EDITOR_VISUALSTUDIO, // TODO
- EDITOR_MONODEVELOP,
- EDITOR_VSCODE
-#elif OSX_ENABLED
- EDITOR_VISUALSTUDIO_MAC,
- EDITOR_MONODEVELOP,
- EDITOR_VSCODE
-#elif 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 cd09e6516a..80a7335b1d 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -30,137 +30,87 @@
#include "godotsharp_export.h"
-#include "../csharp_script.h"
-#include "../godotsharp_defs.h"
-#include "../godotsharp_dirs.h"
-#include "godotsharp_builds.h"
+#include <mono/metadata/image.h>
-void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) {
+#include "../mono_gd/gd_mono.h"
+#include "../mono_gd/gd_mono_assembly.h"
- if (p_type != CSharpLanguage::get_singleton()->get_type())
- return;
+String get_assemblyref_name(MonoImage *p_image, int index) {
+ const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
- ERR_FAIL_COND(p_path.get_extension() != CSharpLanguage::get_singleton()->get_extension());
+ uint32_t cols[MONO_ASSEMBLYREF_SIZE];
- // TODO what if the source file is not part of the game's C# project
+ mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
- 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();
- }
-}
-
-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(GDMono::get_singleton()->get_tools_domain());
-
- String build_config = p_debug ? "Debug" : "Release";
-
- ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config));
-
- // Add API assemblies
-
- String core_api_dll_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(API_ASSEMBLY_NAME ".dll");
- ERR_FAIL_COND(!_add_assembly(core_api_dll_path, core_api_dll_path));
-
- String editor_api_dll_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
- ERR_FAIL_COND(!_add_assembly(editor_api_dll_path, editor_api_dll_path));
-
- // Add project assembly
-
- String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name");
- if (project_dll_name.empty()) {
- project_dll_name = "UnnamedProject";
- }
-
- String project_dll_src_path = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config).plus_file(project_dll_name + ".dll");
- String project_dll_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(project_dll_name + ".dll");
- ERR_FAIL_COND(!_add_assembly(project_dll_src_path, project_dll_dst_path));
-
- // Add dependencies
-
- MonoDomain *prev_domain = mono_domain_get();
- MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
-
- ERR_FAIL_COND(!export_domain);
- ERR_FAIL_COND(!mono_domain_set(export_domain, false));
-
- Map<String, String> dependencies;
- dependencies.insert("mscorlib", GDMono::get_singleton()->get_corlib_assembly()->get_path());
-
- GDMonoAssembly *scripts_assembly = GDMonoAssembly::load_from(project_dll_name, project_dll_src_path, /* refonly: */ true);
-
- ERR_EXPLAIN("Cannot load refonly assembly: " + project_dll_name);
- ERR_FAIL_COND(!scripts_assembly);
-
- Error depend_error = _get_assembly_dependencies(scripts_assembly, dependencies);
-
- GDMono::get_singleton()->finalize_and_unload_domain(export_domain);
- mono_domain_set(prev_domain, false);
-
- 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_assembly(depend_src_path, depend_dst_path));
- }
-}
-
-bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_dst_path) {
-
- 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, false);
-
- return true;
+ return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
}
-Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, 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 (ref_name == "mscorlib" || r_dependencies.find(ref_name))
+ if (r_dependencies.has(ref_name))
continue;
GDMonoAssembly *ref_assembly = NULL;
- if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true)) {
- ERR_EXPLAIN("Cannot load refonly assembly: " + ref_name);
- ERR_FAIL_V(ERR_CANT_RESOLVE);
+ String path;
+ bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe");
+
+ for (int j = 0; j < p_search_dirs.size(); j++) {
+ const String &search_dir = p_search_dirs[j];
+
+ if (has_extension) {
+ path = search_dir.plus_file(ref_name);
+ if (FileAccess::exists(path)) {
+ GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true);
+ if (ref_assembly != NULL)
+ break;
+ }
+ } else {
+ path = search_dir.plus_file(ref_name + ".dll");
+ if (FileAccess::exists(path)) {
+ GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
+ if (ref_assembly != NULL)
+ break;
+ }
+
+ path = search_dir.plus_file(ref_name + ".exe");
+ if (FileAccess::exists(path)) {
+ GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
+ if (ref_assembly != NULL)
+ break;
+ }
+ }
}
- r_dependencies.insert(ref_name, ref_assembly->get_path());
+ ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
- Error err = _get_assembly_dependencies(ref_assembly, r_dependencies);
- if (err != OK)
- return err;
+ r_dependencies[ref_name] = ref_assembly->get_path();
+
+ Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
}
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_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'.");
+
+ 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 b38db9660c..8d121a6bc3 100644
--- a/modules/mono/editor/godotsharp_export.h
+++ b/modules/mono/editor/godotsharp_export.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -31,27 +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_assembly(const String &p_src_path, const String &p_dst_path);
-
- Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, Map<String, String> &r_dependencies);
+namespace GodotSharpExport {
-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);
+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);
-public:
- 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 ecc3e4c59e..0000000000
--- a/modules/mono/editor/mono_bottom_panel.cpp
+++ /dev/null
@@ -1,489 +0,0 @@
-/*************************************************************************/
-/* mono_bottom_panel.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 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 "../csharp_script.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_SUCCESS) {
- 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);
-
- 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() {
-
- GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
-
- MonoReloadNode::get_singleton()->restart_reload_timer();
- CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true);
-}
-
-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("msbuild_log.txt"));
- }
-}
-
-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("msbuild_issues.csv"));
- _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_info(p_build_info),
- logs_dir(p_logs_dir),
- build_exited(false),
- issues_list(memnew(ItemList)),
- error_count(0),
- warning_count(0),
- errors_visible(true),
- warnings_visible(true) {
- 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 03240e9a13..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-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 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;
-
- Button *warnings_btn;
- Button *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/net_solution.cpp b/modules/mono/editor/net_solution.cpp
deleted file mode 100644
index 8bbd376c9a..0000000000
--- a/modules/mono/editor/net_solution.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/*************************************************************************/
-/* net_solution.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 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 "net_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 NETSolution::add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs) {
- if (projects.has(p_name))
- WARN_PRINT("Overriding existing project.");
-
- ProjectInfo procinfo;
- procinfo.guid = p_guid;
-
- procinfo.configs.push_back("Debug");
- procinfo.configs.push_back("Release");
-
- for (int i = 0; i < p_extra_configs.size(); i++) {
- procinfo.configs.push_back(p_extra_configs[i]);
- }
-
- projects[p_name] = procinfo;
-}
-
-Error NETSolution::save() {
- bool dir_exists = DirAccess::exists(path);
- ERR_EXPLAIN("The directory does not exist.");
- ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH);
-
- 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 &procinfo = E->value();
-
- projs_decl += sformat(PROJECT_DECLARATION, name, name + ".csproj", procinfo.guid);
-
- for (int i = 0; i < procinfo.configs.size(); i++) {
- const String &config = procinfo.configs[i];
-
- if (i != 0) {
- sln_platform_cfg += "\n";
- proj_platform_cfg += "\n";
- }
-
- sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config);
- proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, procinfo.guid, config);
- }
- }
-
- String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg);
-
- FileAccessRef file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE);
- ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE);
- file->store_string(content);
- file->close();
-
- return OK;
-}
-
-bool NETSolution::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;
-}
-
-NETSolution::NETSolution(const String &p_name) {
- name = p_name;
-}
diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp
new file mode 100644
index 0000000000..dcb0ca5a80
--- /dev/null
+++ b/modules/mono/editor/script_class_parser.cpp
@@ -0,0 +1,676 @@
+/*************************************************************************/
+/* script_class_parser.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 "script_class_parser.h"
+
+#include "core/map.h"
+#include "core/os/os.h"
+
+#include "../utils/string_utils.h"
+
+const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = {
+ "[",
+ "]",
+ "{",
+ "}",
+ ".",
+ ":",
+ ",",
+ "Symbol",
+ "Identifier",
+ "String",
+ "Number",
+ "<",
+ ">",
+ "EOF",
+ "Error"
+};
+
+String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) {
+
+ ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>");
+ return token_names[p_token];
+}
+
+ScriptClassParser::Token ScriptClassParser::get_token() {
+
+ while (true) {
+ switch (code[idx]) {
+ case '\n': {
+ line++;
+ idx++;
+ break;
+ };
+ case 0: {
+ return TK_EOF;
+ } break;
+ case '{': {
+ idx++;
+ return TK_CURLY_BRACKET_OPEN;
+ };
+ case '}': {
+ idx++;
+ return TK_CURLY_BRACKET_CLOSE;
+ };
+ case '[': {
+ idx++;
+ return TK_BRACKET_OPEN;
+ };
+ case ']': {
+ idx++;
+ return TK_BRACKET_CLOSE;
+ };
+ case '<': {
+ idx++;
+ return TK_OP_LESS;
+ };
+ case '>': {
+ idx++;
+ return TK_OP_GREATER;
+ };
+ case ':': {
+ idx++;
+ return TK_COLON;
+ };
+ case ',': {
+ idx++;
+ return TK_COMMA;
+ };
+ case '.': {
+ idx++;
+ return TK_PERIOD;
+ };
+ case '#': {
+ //compiler directive
+ while (code[idx] != '\n' && code[idx] != 0) {
+ idx++;
+ }
+ continue;
+ } break;
+ case '/': {
+ switch (code[idx + 1]) {
+ case '*': { // block comment
+ idx += 2;
+ while (true) {
+ if (code[idx] == 0) {
+ error_str = "Unterminated comment";
+ error = true;
+ return TK_ERROR;
+ } else if (code[idx] == '*' && code[idx + 1] == '/') {
+ idx += 2;
+ break;
+ } else if (code[idx] == '\n') {
+ line++;
+ }
+
+ idx++;
+ }
+
+ } break;
+ case '/': { // line comment skip
+ while (code[idx] != '\n' && code[idx] != 0) {
+ idx++;
+ }
+
+ } break;
+ default: {
+ value = "/";
+ idx++;
+ return TK_SYMBOL;
+ }
+ }
+
+ continue; // a comment
+ } break;
+ case '\'':
+ case '"': {
+ bool verbatim = idx != 0 && code[idx - 1] == '@';
+
+ CharType begin_str = code[idx];
+ idx++;
+ String tk_string = String();
+ while (true) {
+ if (code[idx] == 0) {
+ error_str = "Unterminated String";
+ error = true;
+ return TK_ERROR;
+ } else if (code[idx] == begin_str) {
+ if (verbatim && code[idx + 1] == '"') { // '""' is verbatim string's '\"'
+ idx += 2; // skip next '"' as well
+ continue;
+ }
+
+ idx += 1;
+ break;
+ } else if (code[idx] == '\\' && !verbatim) {
+ //escaped characters...
+ idx++;
+ CharType next = code[idx];
+ if (next == 0) {
+ error_str = "Unterminated String";
+ error = true;
+ return TK_ERROR;
+ }
+ CharType res = 0;
+
+ switch (next) {
+ case 'b': res = 8; break;
+ case 't': res = 9; break;
+ case 'n': res = 10; break;
+ case 'f': res = 12; break;
+ case 'r':
+ res = 13;
+ break;
+ case '\"': res = '\"'; break;
+ case '\\':
+ res = '\\';
+ break;
+ default: {
+ res = next;
+ } break;
+ }
+
+ tk_string += res;
+
+ } else {
+ if (code[idx] == '\n')
+ line++;
+ tk_string += code[idx];
+ }
+ idx++;
+ }
+
+ value = tk_string;
+
+ return TK_STRING;
+ } break;
+ default: {
+ if (code[idx] <= 32) {
+ idx++;
+ break;
+ }
+
+ if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) {
+ value = String::chr(code[idx]);
+ idx++;
+ return TK_SYMBOL;
+ }
+
+ if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
+ //a number
+ const CharType *rptr;
+ double number = String::to_double(&code[idx], &rptr);
+ idx += (rptr - &code[idx]);
+ value = number;
+ return TK_NUMBER;
+
+ } else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
+ String id;
+
+ id += code[idx];
+ idx++;
+
+ while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) {
+ id += code[idx];
+ idx++;
+ }
+
+ value = id;
+ return TK_IDENTIFIER;
+ } else if (code[idx] == '@' && code[idx + 1] == '"') {
+ // begin of verbatim string
+ idx++;
+ } else {
+ error_str = "Unexpected character.";
+ error = true;
+ return TK_ERROR;
+ }
+ }
+ }
+ }
+}
+
+Error ScriptClassParser::_skip_generic_type_params() {
+
+ Token tk;
+
+ while (true) {
+ tk = get_token();
+
+ if (tk == TK_IDENTIFIER) {
+ tk = get_token();
+ // Type specifications can end with "?" to denote nullable types, such as IList<int?>
+ if (tk == TK_SYMBOL) {
+ tk = get_token();
+ if (value.operator String() != "?") {
+ error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found unexpected symbol '" + value + "'";
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ if (tk != TK_OP_GREATER && tk != TK_COMMA) {
+ error_str = "Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name(tk) + " next.";
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ if (tk == TK_PERIOD) {
+ while (true) {
+ tk = get_token();
+
+ if (tk != TK_IDENTIFIER) {
+ error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ tk = get_token();
+
+ if (tk != TK_PERIOD)
+ break;
+ }
+ }
+
+ if (tk == TK_OP_LESS) {
+ Error err = _skip_generic_type_params();
+ if (err)
+ return err;
+ continue;
+ } else if (tk == TK_OP_GREATER) {
+ return OK;
+ } else if (tk != TK_COMMA) {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ } else if (tk == TK_OP_LESS) {
+ error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS);
+ error = true;
+ return ERR_PARSE_ERROR;
+ } else if (tk == TK_OP_GREATER) {
+ return OK;
+ } else {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ }
+}
+
+Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {
+
+ Token tk = get_token();
+
+ if (tk != TK_IDENTIFIER) {
+ error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ r_full_name += String(value);
+
+ if (code[idx] == '<') {
+ idx++;
+
+ // We don't mind if the base is generic, but we skip it any ways since this information is not needed
+ Error err = _skip_generic_type_params();
+ if (err)
+ return err;
+ }
+
+ if (code[idx] != '.') // We only want to take the next token if it's a period
+ return OK;
+
+ tk = get_token();
+
+ CRASH_COND(tk != TK_PERIOD); // Assertion
+
+ r_full_name += ".";
+
+ return _parse_type_full_name(r_full_name);
+}
+
+Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) {
+
+ String name;
+
+ Error err = _parse_type_full_name(name);
+ if (err)
+ return err;
+
+ Token tk = get_token();
+
+ if (tk == TK_COMMA) {
+ err = _parse_class_base(r_base);
+ if (err)
+ return err;
+ } else if (tk == TK_IDENTIFIER && String(value) == "where") {
+ err = _parse_type_constraints();
+ if (err) {
+ return err;
+ }
+
+ // An open curly bracket was parsed by _parse_type_constraints, so we can exit
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ // we are finished when we hit the open curly bracket
+ } else {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ r_base.push_back(name);
+
+ return OK;
+}
+
+Error ScriptClassParser::_parse_type_constraints() {
+ Token tk = get_token();
+ if (tk != TK_IDENTIFIER) {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ tk = get_token();
+ if (tk != TK_COLON) {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ while (true) {
+ tk = get_token();
+ if (tk == TK_IDENTIFIER) {
+ if (String(value) == "where") {
+ return _parse_type_constraints();
+ }
+
+ tk = get_token();
+ if (tk == TK_PERIOD) {
+ while (true) {
+ tk = get_token();
+
+ if (tk != TK_IDENTIFIER) {
+ error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ tk = get_token();
+
+ if (tk != TK_PERIOD)
+ break;
+ }
+ }
+ }
+
+ if (tk == TK_COMMA) {
+ continue;
+ } else if (tk == TK_IDENTIFIER && String(value) == "where") {
+ return _parse_type_constraints();
+ } else if (tk == TK_SYMBOL && String(value) == "(") {
+ tk = get_token();
+ if (tk != TK_SYMBOL || String(value) != ")") {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ } else if (tk == TK_OP_LESS) {
+ Error err = _skip_generic_type_params();
+ if (err)
+ return err;
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ return OK;
+ } else {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ }
+}
+
+Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) {
+
+ Token tk = get_token();
+
+ if (tk == TK_IDENTIFIER) {
+ r_name += String(value);
+ } else {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ tk = get_token();
+
+ if (tk == TK_PERIOD) {
+ r_name += ".";
+ return _parse_namespace_name(r_name, r_curly_stack);
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ r_curly_stack++;
+ return OK;
+ } else {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+}
+
+Error ScriptClassParser::parse(const String &p_code) {
+
+ code = p_code;
+ idx = 0;
+ line = 0;
+ error_str = String();
+ error = false;
+ value = Variant();
+ classes.clear();
+
+ Token tk = get_token();
+
+ Map<int, NameDecl> name_stack;
+ int curly_stack = 0;
+ int type_curly_stack = 0;
+
+ while (!error && tk != TK_EOF) {
+ if (tk == TK_IDENTIFIER && String(value) == "class") {
+ tk = get_token();
+
+ if (tk == TK_IDENTIFIER) {
+ String name = value;
+ int at_level = type_curly_stack;
+
+ ClassDecl class_decl;
+
+ for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) {
+ const NameDecl &name_decl = E->value();
+
+ if (name_decl.type == NameDecl::NAMESPACE_DECL) {
+ if (E != name_stack.front())
+ class_decl.namespace_ += ".";
+ class_decl.namespace_ += name_decl.name;
+ } else {
+ class_decl.name += name_decl.name + ".";
+ }
+ }
+
+ class_decl.name += name;
+ class_decl.nested = type_curly_stack > 0;
+
+ bool generic = false;
+
+ while (true) {
+ tk = get_token();
+
+ if (tk == TK_COLON) {
+ Error err = _parse_class_base(class_decl.base);
+ if (err)
+ return err;
+
+ curly_stack++;
+ type_curly_stack++;
+
+ break;
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ curly_stack++;
+ type_curly_stack++;
+ break;
+ } else if (tk == TK_OP_LESS && !generic) {
+ generic = true;
+
+ Error err = _skip_generic_type_params();
+ if (err)
+ return err;
+ } else if (tk == TK_IDENTIFIER && String(value) == "where") {
+ Error err = _parse_type_constraints();
+ if (err) {
+ return err;
+ }
+
+ // An open curly bracket was parsed by _parse_type_constraints, so we can exit
+ curly_stack++;
+ type_curly_stack++;
+ break;
+ } else {
+ error_str = "Unexpected token: " + get_token_name(tk);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ NameDecl name_decl;
+ name_decl.name = name;
+ name_decl.type = NameDecl::CLASS_DECL;
+ name_stack[at_level] = name_decl;
+
+ if (!generic) { // no generics, thanks
+ classes.push_back(class_decl);
+ } else if (OS::get_singleton()->is_stdout_verbose()) {
+ String full_name = class_decl.namespace_;
+ if (full_name.length())
+ full_name += ".";
+ full_name += class_decl.name;
+ OS::get_singleton()->print("Ignoring generic class declaration: %s\n", class_decl.name.utf8().get_data());
+ }
+ }
+ } else if (tk == TK_IDENTIFIER && String(value) == "struct") {
+ String name;
+ int at_level = type_curly_stack;
+ while (true) {
+ tk = get_token();
+ if (tk == TK_IDENTIFIER && name.empty()) {
+ name = String(value);
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ if (name.empty()) {
+ error_str = "Expected " + get_token_name(TK_IDENTIFIER) + " after keyword 'struct', found " + get_token_name(TK_CURLY_BRACKET_OPEN);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ curly_stack++;
+ type_curly_stack++;
+ break;
+ } else if (tk == TK_EOF) {
+ error_str = "Expected " + get_token_name(TK_CURLY_BRACKET_OPEN) + " after struct decl, found " + get_token_name(TK_EOF);
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ NameDecl name_decl;
+ name_decl.name = name;
+ name_decl.type = NameDecl::STRUCT_DECL;
+ name_stack[at_level] = name_decl;
+ } else if (tk == TK_IDENTIFIER && String(value) == "namespace") {
+ if (type_curly_stack > 0) {
+ error_str = "Found namespace nested inside type.";
+ error = true;
+ return ERR_PARSE_ERROR;
+ }
+
+ String name;
+ int at_level = curly_stack;
+
+ Error err = _parse_namespace_name(name, curly_stack);
+ if (err)
+ return err;
+
+ NameDecl name_decl;
+ name_decl.name = name;
+ name_decl.type = NameDecl::NAMESPACE_DECL;
+ name_stack[at_level] = name_decl;
+ } else if (tk == TK_CURLY_BRACKET_OPEN) {
+ curly_stack++;
+ } else if (tk == TK_CURLY_BRACKET_CLOSE) {
+ curly_stack--;
+ if (name_stack.has(curly_stack)) {
+ if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL)
+ type_curly_stack--;
+ name_stack.erase(curly_stack);
+ }
+ }
+
+ tk = get_token();
+ }
+
+ if (!error && tk == TK_EOF && curly_stack > 0) {
+ error_str = "Reached EOF with missing close curly brackets.";
+ error = true;
+ }
+
+ if (error)
+ return ERR_PARSE_ERROR;
+
+ return OK;
+}
+
+Error ScriptClassParser::parse_file(const String &p_filepath) {
+
+ String source;
+
+ Error ferr = read_all_file_utf8(p_filepath, source);
+
+ ERR_FAIL_COND_V_MSG(ferr != OK, ferr,
+ ferr == ERR_INVALID_DATA ?
+ "File '" + p_filepath + "' contains invalid unicode (UTF-8), so it was not loaded."
+ " Please ensure that scripts are saved in valid UTF-8 unicode." :
+ "Failed to read file: '" + p_filepath + "'.");
+
+ return parse(source);
+}
+
+String ScriptClassParser::get_error() {
+ return error_str;
+}
+
+Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() {
+ return classes;
+}
diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h
new file mode 100644
index 0000000000..39003336ac
--- /dev/null
+++ b/modules/mono/editor/script_class_parser.h
@@ -0,0 +1,110 @@
+/*************************************************************************/
+/* script_class_parser.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 SCRIPT_CLASS_PARSER_H
+#define SCRIPT_CLASS_PARSER_H
+
+#include "core/ustring.h"
+#include "core/variant.h"
+#include "core/vector.h"
+
+class ScriptClassParser {
+
+public:
+ struct NameDecl {
+ enum Type {
+ NAMESPACE_DECL,
+ CLASS_DECL,
+ STRUCT_DECL
+ };
+
+ String name;
+ Type type;
+ };
+
+ struct ClassDecl {
+ String name;
+ String namespace_;
+ Vector<String> base;
+ bool nested;
+ bool has_script_attr;
+ };
+
+private:
+ String code;
+ int idx;
+ int line;
+ String error_str;
+ bool error;
+ Variant value;
+
+ Vector<ClassDecl> classes;
+
+ enum Token {
+ TK_BRACKET_OPEN,
+ TK_BRACKET_CLOSE,
+ TK_CURLY_BRACKET_OPEN,
+ TK_CURLY_BRACKET_CLOSE,
+ TK_PERIOD,
+ TK_COLON,
+ TK_COMMA,
+ TK_SYMBOL,
+ TK_IDENTIFIER,
+ TK_STRING,
+ TK_NUMBER,
+ TK_OP_LESS,
+ TK_OP_GREATER,
+ TK_EOF,
+ TK_ERROR,
+ TK_MAX
+ };
+
+ static const char *token_names[TK_MAX];
+ static String get_token_name(Token p_token);
+
+ Token get_token();
+
+ Error _skip_generic_type_params();
+
+ Error _parse_type_full_name(String &r_full_name);
+ Error _parse_class_base(Vector<String> &r_base);
+ Error _parse_type_constraints();
+ Error _parse_namespace_name(String &r_name, int &r_curly_stack);
+
+public:
+ Error parse(const String &p_code);
+ Error parse_file(const String &p_filepath);
+
+ String get_error();
+
+ Vector<ClassDecl> get_classes();
+};
+
+#endif // SCRIPT_CLASS_PARSER_H
diff --git a/modules/mono/glue/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 66490b5e25..98a73382f4 100644
--- a/modules/mono/glue/Managed/Files/AABB.cs
+++ b/modules/mono/glue/Managed/Files/AABB.cs
@@ -5,6 +5,7 @@
// file: core/variant_call.cpp
// commit: 5ad9be4c24e9d7dc5672fdc42cea896622fe5685
using System;
+using System.Runtime.InteropServices;
#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
@@ -13,6 +14,8 @@ using real_t = System.Single;
namespace Godot
{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
public struct AABB : IEquatable<AABB>
{
private Vector3 _position;
@@ -407,13 +410,28 @@ namespace Godot
return new AABB(min, max - min);
}
-
- // Constructors
+
+ // Constructors
public AABB(Vector3 position, Vector3 size)
{
_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/Array.cs b/modules/mono/glue/Managed/Files/Array.cs
index c80cb7cc83..0e7b0362e0 100644
--- a/modules/mono/glue/Managed/Files/Array.cs
+++ b/modules/mono/glue/Managed/Files/Array.cs
@@ -28,7 +28,7 @@ namespace Godot.Collections
}
}
- public class Array : IList<object>, ICollection<object>, IEnumerable<object>, IDisposable
+ public class Array : IList, IDisposable
{
ArraySafeHandle safeHandle;
bool disposed = false;
@@ -38,6 +38,14 @@ namespace Godot.Collections
safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor());
}
+ public Array(IEnumerable collection) : this()
+ {
+ if (collection == null)
+ throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'");
+
+ MarshalUtils.EnumerableToArray(collection, GetPtr());
+ }
+
internal Array(ArraySafeHandle handle)
{
safeHandle = handle;
@@ -50,9 +58,19 @@ namespace Godot.Collections
internal IntPtr GetPtr()
{
+ if (disposed)
+ throw new ObjectDisposedException(GetType().FullName);
+
return safeHandle.DangerousGetHandle();
}
+ public Error Resize(int newSize)
+ {
+ return godot_icall_Array_Resize(GetPtr(), newSize);
+ }
+
+ // IDisposable
+
public void Dispose()
{
if (disposed)
@@ -67,94 +85,67 @@ namespace Godot.Collections
disposed = true;
}
+ // IList
+
+ public bool IsReadOnly => false;
+
+ public bool IsFixedSize => false;
+
public object this[int index]
{
- get
- {
- return godot_icall_Array_At(GetPtr(), index);
- }
- set
- {
- godot_icall_Array_SetAt(GetPtr(), index, value);
- }
+ get => godot_icall_Array_At(GetPtr(), index);
+ set => godot_icall_Array_SetAt(GetPtr(), index, value);
}
- public int Count
- {
- get
- {
- return godot_icall_Array_Count(GetPtr());
- }
- }
+ public int Add(object value) => godot_icall_Array_Add(GetPtr(), value);
- public bool IsReadOnly
- {
- get
- {
- return false;
- }
- }
+ public bool Contains(object value) => godot_icall_Array_Contains(GetPtr(), value);
- public void Add(object item)
- {
- godot_icall_Array_Add(GetPtr(), item);
- }
+ public void Clear() => godot_icall_Array_Clear(GetPtr());
- public void Clear()
- {
- godot_icall_Array_Clear(GetPtr());
- }
+ public int IndexOf(object value) => godot_icall_Array_IndexOf(GetPtr(), value);
- public bool Contains(object item)
- {
- return godot_icall_Array_Contains(GetPtr(), item);
- }
+ public void Insert(int index, object value) => godot_icall_Array_Insert(GetPtr(), index, value);
+
+ public void Remove(object value) => godot_icall_Array_Remove(GetPtr(), value);
+
+ public void RemoveAt(int index) => godot_icall_Array_RemoveAt(GetPtr(), index);
+
+ // ICollection
+
+ public int Count => godot_icall_Array_Count(GetPtr());
- public void CopyTo(object[] array, int arrayIndex)
+ public object SyncRoot => this;
+
+ public bool IsSynchronized => false;
+
+ public void CopyTo(System.Array array, int index)
{
if (array == null)
throw new ArgumentNullException(nameof(array), "Value cannot be null.");
- if (arrayIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension.");
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension.");
// Internal call may throw ArgumentException
- godot_icall_Array_CopyTo(GetPtr(), array, arrayIndex);
+ godot_icall_Array_CopyTo(GetPtr(), array, index);
}
- public IEnumerator<object> GetEnumerator()
+ // IEnumerable
+
+ public IEnumerator GetEnumerator()
{
int count = Count;
for (int i = 0; i < count; i++)
{
- yield return godot_icall_Array_At(GetPtr(), i);
+ yield return this[i];
}
}
- public int IndexOf(object item)
- {
- return godot_icall_Array_IndexOf(GetPtr(), item);
- }
-
- public void Insert(int index, object item)
+ public override string ToString()
{
- godot_icall_Array_Insert(GetPtr(), index, item);
- }
-
- public bool Remove(object item)
- {
- return godot_icall_Array_Remove(GetPtr(), item);
- }
-
- public void RemoveAt(int index)
- {
- godot_icall_Array_RemoveAt(GetPtr(), index);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
+ return godot_icall_Array_ToString(GetPtr());
}
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -167,13 +158,16 @@ namespace Godot.Collections
internal extern static object godot_icall_Array_At(IntPtr ptr, int index);
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Array_SetAt(IntPtr ptr, int index, object value);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static int godot_icall_Array_Count(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static void godot_icall_Array_Add(IntPtr ptr, object item);
+ internal extern static int godot_icall_Array_Add(IntPtr ptr, object item);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Array_Clear(IntPtr ptr);
@@ -182,7 +176,7 @@ namespace Godot.Collections
internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, object[] array, int arrayIndex);
+ internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item);
@@ -195,17 +189,42 @@ namespace Godot.Collections
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Array_RemoveAt(IntPtr ptr, int index);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static Error godot_icall_Array_Resize(IntPtr ptr, int newSize);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static string godot_icall_Array_ToString(IntPtr ptr);
}
public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T>
{
Array objectArray;
+ internal static int elemTypeEncoding;
+ internal static IntPtr elemTypeClass;
+
+ static Array()
+ {
+ Array.godot_icall_Array_Generic_GetElementTypeInfo(typeof(T), out elemTypeEncoding, out elemTypeClass);
+ }
+
public Array()
{
objectArray = new Array();
}
+ public Array(IEnumerable<T> collection)
+ {
+ if (collection == null)
+ throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'");
+
+ objectArray = new Array(collection);
+ }
+
public Array(Array array)
{
objectArray = array;
@@ -221,16 +240,28 @@ namespace Godot.Collections
objectArray = new Array(handle);
}
+ internal IntPtr GetPtr()
+ {
+ return objectArray.GetPtr();
+ }
+
public static explicit operator Array(Array<T> from)
{
return from.objectArray;
}
+ public Error Resize(int newSize)
+ {
+ return objectArray.Resize(newSize);
+ }
+
+ // IList<T>
+
public T this[int index]
{
get
{
- return (T)objectArray[index];
+ return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass);
}
set
{
@@ -238,6 +269,23 @@ namespace Godot.Collections
}
}
+ public int IndexOf(T item)
+ {
+ return objectArray.IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ objectArray.Insert(index, item);
+ }
+
+ public void RemoveAt(int index)
+ {
+ objectArray.RemoveAt(index);
+ }
+
+ // ICollection<T>
+
public int Count
{
get
@@ -287,49 +335,33 @@ namespace Godot.Collections
for (int i = 0; i < count; i++)
{
- array[arrayIndex] = (T)objectArray[i];
+ array[arrayIndex] = (T)this[i];
arrayIndex++;
}
}
+ public bool Remove(T item)
+ {
+ return Array.godot_icall_Array_Remove(GetPtr(), item);
+ }
+
+ // IEnumerable<T>
+
public IEnumerator<T> GetEnumerator()
{
int count = objectArray.Count;
for (int i = 0; i < count; i++)
{
- yield return (T)objectArray[i];
+ yield return (T)this[i];
}
}
- public int IndexOf(T item)
- {
- return objectArray.IndexOf(item);
- }
-
- public void Insert(int index, T item)
- {
- objectArray.Insert(index, item);
- }
-
- public bool Remove(T item)
- {
- return objectArray.Remove(item);
- }
-
- public void RemoveAt(int index)
- {
- objectArray.RemoveAt(index);
- }
-
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
- internal IntPtr GetPtr()
- {
- return objectArray.GetPtr();
- }
+ public override string ToString() => objectArray.ToString();
}
}
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 a5618cb28d..0eb76e9c63 100644
--- a/modules/mono/glue/Managed/Files/Basis.cs
+++ b/modules/mono/glue/Managed/Files/Basis.cs
@@ -8,111 +8,119 @@ using real_t = System.Single;
namespace Godot
{
+ [Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Basis : IEquatable<Basis>
{
- private static readonly Basis identity = new Basis
- (
- new Vector3(1f, 0f, 0f),
- new Vector3(0f, 1f, 0f),
- new Vector3(0f, 0f, 1f)
- );
-
- private static readonly Basis[] orthoBases = {
- new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f),
- new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f),
- new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f),
- new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f),
- new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f),
- new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f),
- new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f),
- new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f),
- new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f),
- new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f),
- new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f),
- new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f),
- new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f),
- new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f),
- new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f),
- new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f),
- new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f),
- new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f),
- new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f),
- new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f),
- new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f),
- new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f),
- new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f),
- new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f)
- };
+ // NOTE: x, y and z are public-only. Use Column0, Column1 and Column2 internally.
+ /// <summary>
+ /// Returns the basis matrix’s x vector.
+ /// This is equivalent to <see cref="Column0"/>.
+ /// </summary>
public Vector3 x
{
- get { return GetAxis(0); }
- set { SetAxis(0, value); }
+ get => Column0;
+ set => Column0 = value;
}
+ /// <summary>
+ /// Returns the basis matrix’s y vector.
+ /// This is equivalent to <see cref="Column1"/>.
+ /// </summary>
public Vector3 y
{
- get { return GetAxis(1); }
- set { SetAxis(1, value); }
+ get => Column1;
+ set => Column1 = value;
}
+ /// <summary>
+ /// Returns the basis matrix’s z vector.
+ /// This is equivalent to <see cref="Column2"/>.
+ /// </summary>
public Vector3 z
{
- get { return GetAxis(2); }
- set { SetAxis(2, value); }
+ get => Column2;
+ set => Column2 = value;
}
- private Vector3 _x;
- private Vector3 _y;
- private Vector3 _z;
+ public Vector3 Row0;
+ public Vector3 Row1;
+ public Vector3 Row2;
- public static Basis Identity
+ public Vector3 Column0
{
- get { return identity; }
+ get => new Vector3(Row0.x, Row1.x, Row2.x);
+ set
+ {
+ this.Row0.x = value.x;
+ this.Row1.x = value.y;
+ this.Row2.x = value.z;
+ }
+ }
+ public Vector3 Column1
+ {
+ get => new Vector3(Row0.y, Row1.y, Row2.y);
+ set
+ {
+ this.Row0.y = value.x;
+ this.Row1.y = value.y;
+ this.Row2.y = value.z;
+ }
+ }
+ public Vector3 Column2
+ {
+ get => new Vector3(Row0.z, Row1.z, Row2.z);
+ set
+ {
+ this.Row0.z = value.x;
+ this.Row1.z = value.y;
+ this.Row2.z = value.z;
+ }
}
public Vector3 Scale
{
get
{
- return new Vector3
+ real_t detSign = Mathf.Sign(Determinant());
+ return detSign * new Vector3
(
- new Vector3(this[0, 0], this[1, 0], this[2, 0]).Length(),
- new Vector3(this[0, 1], this[1, 1], this[2, 1]).Length(),
- new Vector3(this[0, 2], this[1, 2], this[2, 2]).Length()
+ new Vector3(this.Row0[0], this.Row1[0], this.Row2[0]).Length(),
+ new Vector3(this.Row0[1], this.Row1[1], this.Row2[1]).Length(),
+ new Vector3(this.Row0[2], this.Row1[2], this.Row2[2]).Length()
);
}
}
- public Vector3 this[int index]
+ public Vector3 this[int columnIndex]
{
get
{
- switch (index)
+ switch (columnIndex)
{
case 0:
- return _x;
+ return Column0;
case 1:
- return _y;
+ return Column1;
case 2:
- return _z;
+ return Column2;
default:
throw new IndexOutOfRangeException();
}
}
set
{
- switch (index)
+ switch (columnIndex)
{
case 0:
- _x = value;
+ Column0 = value;
return;
case 1:
- _y = value;
+ Column1 = value;
return;
case 2:
- _z = value;
+ Column2 = value;
return;
default:
throw new IndexOutOfRangeException();
@@ -120,51 +128,53 @@ namespace Godot
}
}
- public real_t this[int index, int axis]
+ public real_t this[int columnIndex, int rowIndex]
{
get
{
- switch (index)
+ switch (columnIndex)
{
case 0:
- return _x[axis];
+ return Column0[rowIndex];
case 1:
- return _y[axis];
+ return Column1[rowIndex];
case 2:
- return _z[axis];
+ return Column2[rowIndex];
default:
throw new IndexOutOfRangeException();
}
}
set
{
- switch (index)
+ switch (columnIndex)
{
case 0:
- _x[axis] = value;
+ {
+ var column0 = Column0;
+ column0[rowIndex] = value;
+ Column0 = column0;
return;
+ }
case 1:
- _y[axis] = value;
+ {
+ var column1 = Column1;
+ column1[rowIndex] = value;
+ Column1 = column1;
return;
+ }
case 2:
- _z[axis] = value;
+ {
+ var column2 = Column2;
+ column2[rowIndex] = value;
+ Column2 = column2;
return;
+ }
default:
throw new IndexOutOfRangeException();
}
}
}
- internal static Basis CreateFromAxes(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis)
- {
- return new Basis
- (
- new Vector3(xAxis.x, yAxis.x, zAxis.x),
- new Vector3(xAxis.y, yAxis.y, zAxis.y),
- new Vector3(xAxis.z, yAxis.z, zAxis.z)
- );
- }
-
internal Quat RotationQuat()
{
Basis orthonormalizedBasis = Orthonormalized();
@@ -178,7 +188,7 @@ namespace Godot
return orthonormalizedBasis.Quat();
}
- internal void SetQuantScale(Quat quat, Vector3 scale)
+ internal void SetQuatScale(Quat quat, Vector3 scale)
{
SetDiagonal(scale);
Rotate(quat);
@@ -191,29 +201,18 @@ namespace Godot
private void SetDiagonal(Vector3 diagonal)
{
- _x = new Vector3(diagonal.x, 0, 0);
- _y = new Vector3(0, diagonal.y, 0);
- _z = new Vector3(0, 0, diagonal.z);
-
+ Row0 = new Vector3(diagonal.x, 0, 0);
+ Row1 = new Vector3(0, diagonal.y, 0);
+ Row2 = new Vector3(0, 0, diagonal.z);
}
public real_t Determinant()
{
- return this[0, 0] * (this[1, 1] * this[2, 2] - this[2, 1] * this[1, 2]) -
- this[1, 0] * (this[0, 1] * this[2, 2] - this[2, 1] * this[0, 2]) +
- this[2, 0] * (this[0, 1] * this[1, 2] - this[1, 1] * this[0, 2]);
- }
+ real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1];
+ real_t cofac10 = Row1[2] * Row2[0] - Row1[0] * Row2[2];
+ real_t cofac20 = Row1[0] * Row2[1] - Row1[1] * Row2[0];
- public Vector3 GetAxis(int axis)
- {
- return new Vector3(this[0, axis], this[1, axis], this[2, axis]);
- }
-
- public void SetAxis(int axis, Vector3 value)
- {
- this[0, axis] = value.x;
- this[1, axis] = value.y;
- this[2, axis] = value.z;
+ return Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20;
}
public Vector3 GetEuler()
@@ -223,32 +222,80 @@ namespace Godot
Vector3 euler;
euler.z = 0.0f;
- real_t mxy = m[1, 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.y = Mathf.Atan2(m[0, 2], m[2, 2]);
- euler.z = Mathf.Atan2(m[1, 0], m[1, 1]);
+ 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]);
}
else
{
euler.x = Mathf.Pi * 0.5f;
- euler.y = -Mathf.Atan2(-m[0, 1], m[0, 0]);
+ euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]);
}
}
else
{
euler.x = -Mathf.Pi * 0.5f;
- euler.y = -Mathf.Atan2(-m[0, 1], m[0, 0]);
+ euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]);
}
return euler;
}
+ public Vector3 GetRow(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ return Row0;
+ case 1:
+ return Row1;
+ case 2:
+ return Row2;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+
+ public void SetRow(int index, Vector3 value)
+ {
+ switch (index)
+ {
+ case 0:
+ Row0 = value;
+ return;
+ case 1:
+ Row1 = value;
+ return;
+ case 2:
+ Row2 = value;
+ return;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+
+ public Vector3 GetColumn(int index)
+ {
+ return this[index];
+ }
+
+ public void SetColumn(int index, Vector3 value)
+ {
+ this[index] = value;
+ }
+
+ [Obsolete("GetAxis is deprecated. Use GetColumn instead.")]
+ public Vector3 GetAxis(int axis)
+ {
+ return new Vector3(this.Row0[axis], this.Row1[axis], this.Row2[axis]);
+ }
+
public int GetOrthogonalIndex()
{
var orth = this;
@@ -257,7 +304,9 @@ namespace Godot
{
for (int j = 0; j < 3; j++)
{
- real_t v = orth[i, j];
+ var row = orth.GetRow(i);
+
+ real_t v = row[j];
if (v > 0.5f)
v = 1.0f;
@@ -266,13 +315,15 @@ namespace Godot
else
v = 0f;
- orth[i, j] = v;
+ row[j] = v;
+
+ orth.SetRow(i, row);
}
}
for (int i = 0; i < 24; i++)
{
- if (orthoBases[i] == orth)
+ if (orth == _orthoBases[i])
return i;
}
@@ -281,57 +332,45 @@ namespace Godot
public Basis Inverse()
{
- var inv = this;
-
- real_t[] co = {
- inv[1, 1] * inv[2, 2] - inv[1, 2] * inv[2, 1],
- inv[1, 2] * inv[2, 0] - inv[1, 0] * inv[2, 2],
- inv[1, 0] * inv[2, 1] - inv[1, 1] * inv[2, 0]
- };
+ real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1];
+ real_t cofac10 = Row1[2] * Row2[0] - Row1[0] * Row2[2];
+ real_t cofac20 = Row1[0] * Row2[1] - Row1[1] * Row2[0];
- real_t det = inv[0, 0] * co[0] + inv[0, 1] * co[1] + inv[0, 2] * co[2];
+ real_t det = Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20;
if (det == 0)
- {
- return new Basis
- (
- real_t.NaN, real_t.NaN, real_t.NaN,
- real_t.NaN, real_t.NaN, real_t.NaN,
- real_t.NaN, real_t.NaN, real_t.NaN
- );
- }
+ throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");
- real_t s = 1.0f / det;
+ real_t detInv = 1.0f / det;
- inv = new Basis
+ real_t cofac01 = Row0[2] * Row2[1] - Row0[1] * Row2[2];
+ real_t cofac02 = Row0[1] * Row1[2] - Row0[2] * Row1[1];
+ real_t cofac11 = Row0[0] * Row2[2] - Row0[2] * Row2[0];
+ real_t cofac12 = Row0[2] * Row1[0] - Row0[0] * Row1[2];
+ real_t cofac21 = Row0[1] * Row2[0] - Row0[0] * Row2[1];
+ real_t cofac22 = Row0[0] * Row1[1] - Row0[1] * Row1[0];
+
+ return new Basis
(
- co[0] * s,
- inv[0, 2] * inv[2, 1] - inv[0, 1] * inv[2, 2] * s,
- inv[0, 1] * inv[1, 2] - inv[0, 2] * inv[1, 1] * s,
- co[1] * s,
- inv[0, 0] * inv[2, 2] - inv[0, 2] * inv[2, 0] * s,
- inv[0, 2] * inv[1, 0] - inv[0, 0] * inv[1, 2] * s,
- co[2] * s,
- inv[0, 1] * inv[2, 0] - inv[0, 0] * inv[2, 1] * s,
- inv[0, 0] * inv[1, 1] - inv[0, 1] * inv[1, 0] * s
+ cofac00 * detInv, cofac01 * detInv, cofac02 * detInv,
+ cofac10 * detInv, cofac11 * detInv, cofac12 * detInv,
+ cofac20 * detInv, cofac21 * detInv, cofac22 * detInv
);
-
- return inv;
}
public Basis Orthonormalized()
{
- Vector3 xAxis = GetAxis(0);
- Vector3 yAxis = GetAxis(1);
- Vector3 zAxis = GetAxis(2);
+ Vector3 column0 = GetColumn(0);
+ Vector3 column1 = GetColumn(1);
+ Vector3 column2 = GetColumn(2);
- xAxis.Normalize();
- yAxis = yAxis - xAxis * xAxis.Dot(yAxis);
- yAxis.Normalize();
- zAxis = zAxis - xAxis * xAxis.Dot(zAxis) - yAxis * yAxis.Dot(zAxis);
- zAxis.Normalize();
+ column0.Normalize();
+ column1 = column1 - column0 * column0.Dot(column1);
+ column1.Normalize();
+ column2 = column2 - column0 * column0.Dot(column2) - column1 * column1.Dot(column2);
+ column2.Normalize();
- return CreateFromAxes(xAxis, yAxis, zAxis);
+ return new Basis(column0, column1, column2);
}
public Basis Rotated(Vector3 axis, real_t phi)
@@ -341,51 +380,43 @@ namespace Godot
public Basis Scaled(Vector3 scale)
{
- var m = this;
-
- m[0, 0] *= scale.x;
- m[0, 1] *= scale.x;
- m[0, 2] *= scale.x;
- m[1, 0] *= scale.y;
- m[1, 1] *= scale.y;
- m[1, 2] *= scale.y;
- m[2, 0] *= scale.z;
- m[2, 1] *= scale.z;
- m[2, 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)
{
- return this[0, 0] * with[0] + this[1, 0] * with[1] + this[2, 0] * with[2];
+ return this.Row0[0] * with[0] + this.Row1[0] * with[1] + this.Row2[0] * with[2];
}
public real_t Tdoty(Vector3 with)
{
- return this[0, 1] * with[0] + this[1, 1] * with[1] + this[2, 1] * with[2];
+ return this.Row0[1] * with[0] + this.Row1[1] * with[1] + this.Row2[1] * with[2];
}
public real_t Tdotz(Vector3 with)
{
- return this[0, 2] * with[0] + this[1, 2] * with[1] + this[2, 2] * with[2];
+ return this.Row0[2] * with[0] + this.Row1[2] * with[1] + this.Row2[2] * with[2];
}
public Basis Transposed()
{
var tr = this;
- real_t temp = tr[0, 1];
- tr[0, 1] = tr[1, 0];
- tr[1, 0] = temp;
+ real_t temp = tr.Row0[1];
+ tr.Row0[1] = tr.Row1[0];
+ tr.Row1[0] = temp;
- temp = tr[0, 2];
- tr[0, 2] = tr[2, 0];
- tr[2, 0] = temp;
+ temp = tr.Row0[2];
+ tr.Row0[2] = tr.Row2[0];
+ tr.Row2[0] = temp;
- temp = tr[1, 2];
- tr[1, 2] = tr[2, 1];
- tr[2, 1] = temp;
+ temp = tr.Row1[2];
+ tr.Row1[2] = tr.Row2[1];
+ tr.Row2[1] = temp;
return tr;
}
@@ -394,9 +425,9 @@ namespace Godot
{
return new Vector3
(
- this[0].Dot(v),
- this[1].Dot(v),
- this[2].Dot(v)
+ this.Row0.Dot(v),
+ this.Row1.Dot(v),
+ this.Row2.Dot(v)
);
}
@@ -404,58 +435,101 @@ namespace Godot
{
return new Vector3
(
- this[0, 0] * v.x + this[1, 0] * v.y + this[2, 0] * v.z,
- this[0, 1] * v.x + this[1, 1] * v.y + this[2, 1] * v.z,
- this[0, 2] * v.x + this[1, 2] * v.y + this[2, 2] * v.z
+ this.Row0[0] * v.x + this.Row1[0] * v.y + this.Row2[0] * v.z,
+ this.Row0[1] * v.x + this.Row1[1] * v.y + this.Row2[1] * v.z,
+ this.Row0[2] * v.x + this.Row1[2] * v.y + this.Row2[2] * v.z
);
}
- public Quat Quat() {
- real_t trace = _x[0] + _y[1] + _z[2];
+ public Quat Quat()
+ {
+ real_t trace = Row0[0] + Row1[1] + Row2[2];
- if (trace > 0.0f) {
+ if (trace > 0.0f)
+ {
real_t s = Mathf.Sqrt(trace + 1.0f) * 2f;
real_t inv_s = 1f / s;
return new Quat(
- (_z[1] - _y[2]) * inv_s,
- (_x[2] - _z[0]) * inv_s,
- (_y[0] - _x[1]) * inv_s,
+ (Row2[1] - Row1[2]) * inv_s,
+ (Row0[2] - Row2[0]) * inv_s,
+ (Row1[0] - Row0[1]) * inv_s,
s * 0.25f
);
}
- if (_x[0] > _y[1] && _x[0] > _z[2]) {
- real_t s = Mathf.Sqrt(_x[0] - _y[1] - _z[2] + 1.0f) * 2f;
+ if (Row0[0] > Row1[1] && Row0[0] > Row2[2])
+ {
+ real_t s = Mathf.Sqrt(Row0[0] - Row1[1] - Row2[2] + 1.0f) * 2f;
real_t inv_s = 1f / s;
return new Quat(
s * 0.25f,
- (_x[1] + _y[0]) * inv_s,
- (_x[2] + _z[0]) * inv_s,
- (_z[1] - _y[2]) * inv_s
+ (Row0[1] + Row1[0]) * inv_s,
+ (Row0[2] + Row2[0]) * inv_s,
+ (Row2[1] - Row1[2]) * inv_s
);
}
- if (_y[1] > _z[2]) {
- real_t s = Mathf.Sqrt(-_x[0] + _y[1] - _z[2] + 1.0f) * 2f;
+ if (Row1[1] > Row2[2])
+ {
+ real_t s = Mathf.Sqrt(-Row0[0] + Row1[1] - Row2[2] + 1.0f) * 2f;
real_t inv_s = 1f / s;
return new Quat(
- (_x[1] + _y[0]) * inv_s,
+ (Row0[1] + Row1[0]) * inv_s,
s * 0.25f,
- (_y[2] + _z[1]) * inv_s,
- (_x[2] - _z[0]) * inv_s
+ (Row1[2] + Row2[1]) * inv_s,
+ (Row0[2] - Row2[0]) * inv_s
);
- } else {
- real_t s = Mathf.Sqrt(-_x[0] - _y[1] + _z[2] + 1.0f) * 2f;
+ }
+ else
+ {
+ real_t s = Mathf.Sqrt(-Row0[0] - Row1[1] + Row2[2] + 1.0f) * 2f;
real_t inv_s = 1f / s;
return new Quat(
- (_x[2] + _z[0]) * inv_s,
- (_y[2] + _z[1]) * inv_s,
+ (Row0[2] + Row2[0]) * inv_s,
+ (Row1[2] + Row2[1]) * inv_s,
s * 0.25f,
- (_y[0] - _x[1]) * inv_s
+ (Row1[0] - Row0[1]) * inv_s
);
}
}
+ private static readonly Basis[] _orthoBases = {
+ new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f),
+ new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f),
+ new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f),
+ new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f),
+ new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f),
+ new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f),
+ new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f),
+ new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f),
+ new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f),
+ new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f),
+ new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f),
+ new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f),
+ new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f),
+ new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f),
+ new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f),
+ new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f),
+ new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f),
+ new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f),
+ new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f),
+ new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f),
+ new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f),
+ new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f),
+ new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f),
+ new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f)
+ };
+
+ private static readonly Basis _identity = new Basis(1, 0, 0, 0, 1, 0, 0, 0, 1);
+ private static readonly Basis _flipX = new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1);
+ private static readonly Basis _flipY = new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1);
+ private static readonly Basis _flipZ = new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1);
+
+ public static Basis Identity { get { return _identity; } }
+ public static Basis FlipX { get { return _flipX; } }
+ public static Basis FlipY { get { return _flipY; } }
+ public static Basis FlipZ { get { return _flipZ; } }
+
public Basis(Quat quat)
{
real_t s = 2.0f / quat.LengthSquared;
@@ -473,9 +547,9 @@ namespace Godot
real_t yz = quat.y * zs;
real_t zz = quat.z * zs;
- _x = new Vector3(1.0f - (yy + zz), xy - wz, xz + wy);
- _y = new Vector3(xy + wz, 1.0f - (xx + zz), yz - wx);
- _z = new Vector3(xz - wy, yz + wx, 1.0f - (xx + yy));
+ Row0 = new Vector3(1.0f - (yy + zz), xy - wz, xz + wy);
+ Row1 = new Vector3(xy + wz, 1.0f - (xx + zz), yz - wx);
+ Row2 = new Vector3(xz - wy, yz + wx, 1.0f - (xx + yy));
}
public Basis(Vector3 euler)
@@ -500,54 +574,58 @@ 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 cosine = Mathf.Cos( phi);
- real_t sine = Mathf.Sin( phi);
+ real_t sine = Mathf.Sin(phi);
+ real_t t = 1.0f - cosine;
- _x = 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;
- _y = 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;
- _z = 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 xAxis, Vector3 yAxis, Vector3 zAxis)
+ public Basis(Vector3 column0, Vector3 column1, Vector3 column2)
{
- _x = xAxis;
- _y = yAxis;
- _z = zAxis;
+ Row0 = new Vector3(column0.x, column1.x, column2.x);
+ Row1 = new Vector3(column0.y, column1.y, column2.y);
+ Row2 = new Vector3(column0.z, column1.z, column2.z);
+ // Same as:
+ // Column0 = column0;
+ // Column1 = column1;
+ // Column2 = column2;
+ // We need to assign the struct fields here first so we can't do it that way...
}
- public 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)
{
- _x = new Vector3(xx, xy, xz);
- _y = new Vector3(yx, yy, yz);
- _z = 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)
{
return new Basis
(
- right.Tdotx(left[0]), right.Tdoty(left[0]), right.Tdotz(left[0]),
- right.Tdotx(left[1]), right.Tdoty(left[1]), right.Tdotz(left[1]),
- right.Tdotx(left[2]), right.Tdoty(left[2]), right.Tdotz(left[2])
+ right.Tdotx(left.Row0), right.Tdoty(left.Row0), right.Tdotz(left.Row0),
+ right.Tdotx(left.Row1), right.Tdoty(left.Row1), right.Tdotz(left.Row1),
+ right.Tdotx(left.Row2), right.Tdoty(left.Row2), right.Tdotz(left.Row2)
);
}
@@ -573,21 +651,21 @@ namespace Godot
public bool Equals(Basis other)
{
- return _x.Equals(other[0]) && _y.Equals(other[1]) && _z.Equals(other[2]);
+ return Row0.Equals(other.Row0) && Row1.Equals(other.Row1) && Row2.Equals(other.Row2);
}
public override int GetHashCode()
{
- return _x.GetHashCode() ^ _y.GetHashCode() ^ _z.GetHashCode();
+ return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode();
}
public override string ToString()
{
return String.Format("({0}, {1}, {2})", new object[]
{
- _x.ToString(),
- _y.ToString(),
- _z.ToString()
+ Row0.ToString(),
+ Row1.ToString(),
+ Row2.ToString()
});
}
@@ -595,9 +673,9 @@ namespace Godot
{
return String.Format("({0}, {1}, {2})", new object[]
{
- _x.ToString(format),
- _y.ToString(format),
- _z.ToString(format)
+ Row0.ToString(format),
+ Row1.ToString(format),
+ Row2.ToString(format)
});
}
}
diff --git a/modules/mono/glue/Managed/Files/Color.cs b/modules/mono/glue/Managed/Files/Color.cs
index 88cb8524b8..3a52a1a13b 100644
--- a/modules/mono/glue/Managed/Files/Color.cs
+++ b/modules/mono/glue/Managed/Files/Color.cs
@@ -1,7 +1,10 @@
using System;
+using System.Runtime.InteropServices;
namespace Godot
{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
public struct Color : IEquatable<Color>
{
public float r;
@@ -13,7 +16,11 @@ namespace Godot
{
get
{
- return (int)(r * 255.0f);
+ return (int)Math.Round(r * 255.0f);
+ }
+ set
+ {
+ r = value / 255.0f;
}
}
@@ -21,7 +28,11 @@ namespace Godot
{
get
{
- return (int)(g * 255.0f);
+ return (int)Math.Round(g * 255.0f);
+ }
+ set
+ {
+ g = value / 255.0f;
}
}
@@ -29,7 +40,11 @@ namespace Godot
{
get
{
- return (int)(b * 255.0f);
+ return (int)Math.Round(b * 255.0f);
+ }
+ set
+ {
+ b = value / 255.0f;
}
}
@@ -37,7 +52,11 @@ namespace Godot
{
get
{
- return (int)(a * 255.0f);
+ return (int)Math.Round(a * 255.0f);
+ }
+ set
+ {
+ a = value / 255.0f;
}
}
@@ -71,7 +90,7 @@ namespace Godot
}
set
{
- this = FromHsv(value, s, v);
+ this = FromHsv(value, s, v, a);
}
}
@@ -88,7 +107,7 @@ namespace Godot
}
set
{
- this = FromHsv(h, value, v);
+ this = FromHsv(h, value, v, a);
}
}
@@ -100,21 +119,30 @@ namespace Godot
}
set
{
- this = FromHsv(h, s, value);
+ this = FromHsv(h, s, value, a);
}
}
- private static readonly Color black = new Color(0f, 0f, 0f);
-
- public Color Black
+ public static Color ColorN(string name, float alpha = 1f)
{
- get
+ name = name.Replace(" ", String.Empty);
+ name = name.Replace("-", String.Empty);
+ name = name.Replace("_", String.Empty);
+ name = name.Replace("'", String.Empty);
+ name = name.Replace(".", String.Empty);
+ name = name.ToLower();
+
+ if (!Colors.namedColors.ContainsKey(name))
{
- return black;
+ throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}");
}
+
+ Color color = Colors.namedColors[name];
+ color.a = alpha;
+ return color;
}
- public float this [int index]
+ public float this[int index]
{
get
{
@@ -154,10 +182,10 @@ namespace Godot
}
}
- public static void ToHsv(Color color, out float hue, out float saturation, out float value)
+ public void ToHsv(out float hue, out float saturation, out float value)
{
- 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 max = (float)Mathf.Max(r, Mathf.Max(g, b));
+ float min = (float)Mathf.Min(r, Mathf.Min(g, b));
float delta = max - min;
@@ -167,12 +195,12 @@ namespace Godot
}
else
{
- if (color.r == max)
- hue = (color.g - color.b) / delta; // Between yellow & magenta
- else if (color.g == max)
- hue = 2 + (color.b - color.r) / delta; // Between cyan & yellow
+ if (r == max)
+ hue = (g - b) / delta; // Between yellow & magenta
+ else if (g == max)
+ hue = 2 + (b - r) / delta; // Between cyan & yellow
else
- hue = 4 + (color.r - color.g) / delta; // Between magenta & cyan
+ hue = 4 + (r - g) / delta; // Between magenta & cyan
hue /= 6.0f;
@@ -181,7 +209,7 @@ namespace Godot
}
saturation = max == 0 ? 0 : 1f - 1f * min / max;
- value = max / 255f;
+ value = max;
}
public static Color FromHsv(float hue, float saturation, float value, float alpha = 1.0f)
@@ -245,7 +273,8 @@ namespace Godot
return new Color(
(r + 0.5f) % 1.0f,
(g + 0.5f) % 1.0f,
- (b + 0.5f) % 1.0f
+ (b + 0.5f) % 1.0f,
+ a
);
}
@@ -263,7 +292,8 @@ namespace Godot
return new Color(
1.0f - r,
1.0f - g,
- 1.0f - b
+ 1.0f - b,
+ a
);
}
@@ -366,7 +396,7 @@ namespace Godot
return c;
}
- public string ToHtml(bool include_alpha = true)
+ public string ToHtml(bool includeAlpha = true)
{
var txt = string.Empty;
@@ -374,12 +404,12 @@ namespace Godot
txt += ToHex32(g);
txt += ToHex32(b);
- if (include_alpha)
+ if (includeAlpha)
txt = ToHex32(a) + txt;
return txt;
}
-
+
// Constructors
public Color(float r, float g, float b, float a = 1.0f)
{
@@ -456,13 +486,13 @@ namespace Godot
for (int i = 0; i < 2; i++)
{
- char[] c = { (char)0, (char)0 };
+ char c;
int lv = v & 0xF;
if (lv < 10)
- c[0] = (char)('0' + lv);
+ c = (char)('0' + lv);
else
- c[0] = (char)('a' + lv - 10);
+ c = (char)('a' + lv - 10);
v >>= 4;
ret = c + ret;
@@ -481,12 +511,17 @@ namespace Godot
bool alpha;
- if (color.Length == 8)
- alpha = true;
- else if (color.Length == 6)
- alpha = false;
- else
- return false;
+ switch (color.Length)
+ {
+ case 8:
+ alpha = true;
+ break;
+ case 6:
+ alpha = false;
+ break;
+ default:
+ return false;
+ }
if (alpha)
{
@@ -582,11 +617,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;
}
@@ -599,11 +634,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;
}
@@ -626,7 +661,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/Colors.cs b/modules/mono/glue/Managed/Files/Colors.cs
new file mode 100644
index 0000000000..f41f5e9fc8
--- /dev/null
+++ b/modules/mono/glue/Managed/Files/Colors.cs
@@ -0,0 +1,305 @@
+using System;
+using System.Collections.Generic;
+
+namespace Godot
+{
+ public static class Colors
+ {
+ // Color names and values are derived from core/color_names.inc
+ internal static readonly Dictionary<string, Color> namedColors = new Dictionary<string, Color> {
+ {"aliceblue", new Color(0.94f, 0.97f, 1.00f)},
+ {"antiquewhite", new Color(0.98f, 0.92f, 0.84f)},
+ {"aqua", new Color(0.00f, 1.00f, 1.00f)},
+ {"aquamarine", new Color(0.50f, 1.00f, 0.83f)},
+ {"azure", new Color(0.94f, 1.00f, 1.00f)},
+ {"beige", new Color(0.96f, 0.96f, 0.86f)},
+ {"bisque", new Color(1.00f, 0.89f, 0.77f)},
+ {"black", new Color(0.00f, 0.00f, 0.00f)},
+ {"blanchedalmond", new Color(1.00f, 0.92f, 0.80f)},
+ {"blue", new Color(0.00f, 0.00f, 1.00f)},
+ {"blueviolet", new Color(0.54f, 0.17f, 0.89f)},
+ {"brown", new Color(0.65f, 0.16f, 0.16f)},
+ {"burlywood", new Color(0.87f, 0.72f, 0.53f)},
+ {"cadetblue", new Color(0.37f, 0.62f, 0.63f)},
+ {"chartreuse", new Color(0.50f, 1.00f, 0.00f)},
+ {"chocolate", new Color(0.82f, 0.41f, 0.12f)},
+ {"coral", new Color(1.00f, 0.50f, 0.31f)},
+ {"cornflower", new Color(0.39f, 0.58f, 0.93f)},
+ {"cornsilk", new Color(1.00f, 0.97f, 0.86f)},
+ {"crimson", new Color(0.86f, 0.08f, 0.24f)},
+ {"cyan", new Color(0.00f, 1.00f, 1.00f)},
+ {"darkblue", new Color(0.00f, 0.00f, 0.55f)},
+ {"darkcyan", new Color(0.00f, 0.55f, 0.55f)},
+ {"darkgoldenrod", new Color(0.72f, 0.53f, 0.04f)},
+ {"darkgray", new Color(0.66f, 0.66f, 0.66f)},
+ {"darkgreen", new Color(0.00f, 0.39f, 0.00f)},
+ {"darkkhaki", new Color(0.74f, 0.72f, 0.42f)},
+ {"darkmagenta", new Color(0.55f, 0.00f, 0.55f)},
+ {"darkolivegreen", new Color(0.33f, 0.42f, 0.18f)},
+ {"darkorange", new Color(1.00f, 0.55f, 0.00f)},
+ {"darkorchid", new Color(0.60f, 0.20f, 0.80f)},
+ {"darkred", new Color(0.55f, 0.00f, 0.00f)},
+ {"darksalmon", new Color(0.91f, 0.59f, 0.48f)},
+ {"darkseagreen", new Color(0.56f, 0.74f, 0.56f)},
+ {"darkslateblue", new Color(0.28f, 0.24f, 0.55f)},
+ {"darkslategray", new Color(0.18f, 0.31f, 0.31f)},
+ {"darkturquoise", new Color(0.00f, 0.81f, 0.82f)},
+ {"darkviolet", new Color(0.58f, 0.00f, 0.83f)},
+ {"deeppink", new Color(1.00f, 0.08f, 0.58f)},
+ {"deepskyblue", new Color(0.00f, 0.75f, 1.00f)},
+ {"dimgray", new Color(0.41f, 0.41f, 0.41f)},
+ {"dodgerblue", new Color(0.12f, 0.56f, 1.00f)},
+ {"firebrick", new Color(0.70f, 0.13f, 0.13f)},
+ {"floralwhite", new Color(1.00f, 0.98f, 0.94f)},
+ {"forestgreen", new Color(0.13f, 0.55f, 0.13f)},
+ {"fuchsia", new Color(1.00f, 0.00f, 1.00f)},
+ {"gainsboro", new Color(0.86f, 0.86f, 0.86f)},
+ {"ghostwhite", new Color(0.97f, 0.97f, 1.00f)},
+ {"gold", new Color(1.00f, 0.84f, 0.00f)},
+ {"goldenrod", new Color(0.85f, 0.65f, 0.13f)},
+ {"gray", new Color(0.75f, 0.75f, 0.75f)},
+ {"green", new Color(0.00f, 1.00f, 0.00f)},
+ {"greenyellow", new Color(0.68f, 1.00f, 0.18f)},
+ {"honeydew", new Color(0.94f, 1.00f, 0.94f)},
+ {"hotpink", new Color(1.00f, 0.41f, 0.71f)},
+ {"indianred", new Color(0.80f, 0.36f, 0.36f)},
+ {"indigo", new Color(0.29f, 0.00f, 0.51f)},
+ {"ivory", new Color(1.00f, 1.00f, 0.94f)},
+ {"khaki", new Color(0.94f, 0.90f, 0.55f)},
+ {"lavender", new Color(0.90f, 0.90f, 0.98f)},
+ {"lavenderblush", new Color(1.00f, 0.94f, 0.96f)},
+ {"lawngreen", new Color(0.49f, 0.99f, 0.00f)},
+ {"lemonchiffon", new Color(1.00f, 0.98f, 0.80f)},
+ {"lightblue", new Color(0.68f, 0.85f, 0.90f)},
+ {"lightcoral", new Color(0.94f, 0.50f, 0.50f)},
+ {"lightcyan", new Color(0.88f, 1.00f, 1.00f)},
+ {"lightgoldenrod", new Color(0.98f, 0.98f, 0.82f)},
+ {"lightgray", new Color(0.83f, 0.83f, 0.83f)},
+ {"lightgreen", new Color(0.56f, 0.93f, 0.56f)},
+ {"lightpink", new Color(1.00f, 0.71f, 0.76f)},
+ {"lightsalmon", new Color(1.00f, 0.63f, 0.48f)},
+ {"lightseagreen", new Color(0.13f, 0.70f, 0.67f)},
+ {"lightskyblue", new Color(0.53f, 0.81f, 0.98f)},
+ {"lightslategray", new Color(0.47f, 0.53f, 0.60f)},
+ {"lightsteelblue", new Color(0.69f, 0.77f, 0.87f)},
+ {"lightyellow", new Color(1.00f, 1.00f, 0.88f)},
+ {"lime", new Color(0.00f, 1.00f, 0.00f)},
+ {"limegreen", new Color(0.20f, 0.80f, 0.20f)},
+ {"linen", new Color(0.98f, 0.94f, 0.90f)},
+ {"magenta", new Color(1.00f, 0.00f, 1.00f)},
+ {"maroon", new Color(0.69f, 0.19f, 0.38f)},
+ {"mediumaquamarine", new Color(0.40f, 0.80f, 0.67f)},
+ {"mediumblue", new Color(0.00f, 0.00f, 0.80f)},
+ {"mediumorchid", new Color(0.73f, 0.33f, 0.83f)},
+ {"mediumpurple", new Color(0.58f, 0.44f, 0.86f)},
+ {"mediumseagreen", new Color(0.24f, 0.70f, 0.44f)},
+ {"mediumslateblue", new Color(0.48f, 0.41f, 0.93f)},
+ {"mediumspringgreen", new Color(0.00f, 0.98f, 0.60f)},
+ {"mediumturquoise", new Color(0.28f, 0.82f, 0.80f)},
+ {"mediumvioletred", new Color(0.78f, 0.08f, 0.52f)},
+ {"midnightblue", new Color(0.10f, 0.10f, 0.44f)},
+ {"mintcream", new Color(0.96f, 1.00f, 0.98f)},
+ {"mistyrose", new Color(1.00f, 0.89f, 0.88f)},
+ {"moccasin", new Color(1.00f, 0.89f, 0.71f)},
+ {"navajowhite", new Color(1.00f, 0.87f, 0.68f)},
+ {"navyblue", new Color(0.00f, 0.00f, 0.50f)},
+ {"oldlace", new Color(0.99f, 0.96f, 0.90f)},
+ {"olive", new Color(0.50f, 0.50f, 0.00f)},
+ {"olivedrab", new Color(0.42f, 0.56f, 0.14f)},
+ {"orange", new Color(1.00f, 0.65f, 0.00f)},
+ {"orangered", new Color(1.00f, 0.27f, 0.00f)},
+ {"orchid", new Color(0.85f, 0.44f, 0.84f)},
+ {"palegoldenrod", new Color(0.93f, 0.91f, 0.67f)},
+ {"palegreen", new Color(0.60f, 0.98f, 0.60f)},
+ {"paleturquoise", new Color(0.69f, 0.93f, 0.93f)},
+ {"palevioletred", new Color(0.86f, 0.44f, 0.58f)},
+ {"papayawhip", new Color(1.00f, 0.94f, 0.84f)},
+ {"peachpuff", new Color(1.00f, 0.85f, 0.73f)},
+ {"peru", new Color(0.80f, 0.52f, 0.25f)},
+ {"pink", new Color(1.00f, 0.75f, 0.80f)},
+ {"plum", new Color(0.87f, 0.63f, 0.87f)},
+ {"powderblue", new Color(0.69f, 0.88f, 0.90f)},
+ {"purple", new Color(0.63f, 0.13f, 0.94f)},
+ {"rebeccapurple", new Color(0.40f, 0.20f, 0.60f)},
+ {"red", new Color(1.00f, 0.00f, 0.00f)},
+ {"rosybrown", new Color(0.74f, 0.56f, 0.56f)},
+ {"royalblue", new Color(0.25f, 0.41f, 0.88f)},
+ {"saddlebrown", new Color(0.55f, 0.27f, 0.07f)},
+ {"salmon", new Color(0.98f, 0.50f, 0.45f)},
+ {"sandybrown", new Color(0.96f, 0.64f, 0.38f)},
+ {"seagreen", new Color(0.18f, 0.55f, 0.34f)},
+ {"seashell", new Color(1.00f, 0.96f, 0.93f)},
+ {"sienna", new Color(0.63f, 0.32f, 0.18f)},
+ {"silver", new Color(0.75f, 0.75f, 0.75f)},
+ {"skyblue", new Color(0.53f, 0.81f, 0.92f)},
+ {"slateblue", new Color(0.42f, 0.35f, 0.80f)},
+ {"slategray", new Color(0.44f, 0.50f, 0.56f)},
+ {"snow", new Color(1.00f, 0.98f, 0.98f)},
+ {"springgreen", new Color(0.00f, 1.00f, 0.50f)},
+ {"steelblue", new Color(0.27f, 0.51f, 0.71f)},
+ {"tan", new Color(0.82f, 0.71f, 0.55f)},
+ {"teal", new Color(0.00f, 0.50f, 0.50f)},
+ {"thistle", new Color(0.85f, 0.75f, 0.85f)},
+ {"tomato", new Color(1.00f, 0.39f, 0.28f)},
+ {"transparent", new Color(1.00f, 1.00f, 1.00f, 0.00f)},
+ {"turquoise", new Color(0.25f, 0.88f, 0.82f)},
+ {"violet", new Color(0.93f, 0.51f, 0.93f)},
+ {"webgreen", new Color(0.00f, 0.50f, 0.00f)},
+ {"webgray", new Color(0.50f, 0.50f, 0.50f)},
+ {"webmaroon", new Color(0.50f, 0.00f, 0.00f)},
+ {"webpurple", new Color(0.50f, 0.00f, 0.50f)},
+ {"wheat", new Color(0.96f, 0.87f, 0.70f)},
+ {"white", new Color(1.00f, 1.00f, 1.00f)},
+ {"whitesmoke", new Color(0.96f, 0.96f, 0.96f)},
+ {"yellow", new Color(1.00f, 1.00f, 0.00f)},
+ {"yellowgreen", new Color(0.60f, 0.80f, 0.20f)},
+ };
+
+ public static Color AliceBlue { get { return namedColors["aliceblue"]; } }
+ public static Color AntiqueWhite { get { return namedColors["antiquewhite"]; } }
+ public static Color Aqua { get { return namedColors["aqua"]; } }
+ public static Color Aquamarine { get { return namedColors["aquamarine"]; } }
+ public static Color Azure { get { return namedColors["azure"]; } }
+ public static Color Beige { get { return namedColors["beige"]; } }
+ public static Color Bisque { get { return namedColors["bisque"]; } }
+ public static Color Black { get { return namedColors["black"]; } }
+ public static Color BlanchedAlmond { get { return namedColors["blanchedalmond"]; } }
+ public static Color Blue { get { return namedColors["blue"]; } }
+ public static Color BlueViolet { get { return namedColors["blueviolet"]; } }
+ public static Color Brown { get { return namedColors["brown"]; } }
+ public static Color BurlyWood { get { return namedColors["burlywood"]; } }
+ public static Color CadetBlue { get { return namedColors["cadetblue"]; } }
+ public static Color Chartreuse { get { return namedColors["chartreuse"]; } }
+ public static Color Chocolate { get { return namedColors["chocolate"]; } }
+ public static Color Coral { get { return namedColors["coral"]; } }
+ public static Color Cornflower { get { return namedColors["cornflower"]; } }
+ public static Color Cornsilk { get { return namedColors["cornsilk"]; } }
+ public static Color Crimson { get { return namedColors["crimson"]; } }
+ public static Color Cyan { get { return namedColors["cyan"]; } }
+ public static Color DarkBlue { get { return namedColors["darkblue"]; } }
+ public static Color DarkCyan { get { return namedColors["darkcyan"]; } }
+ public static Color DarkGoldenrod { get { return namedColors["darkgoldenrod"]; } }
+ public static Color DarkGray { get { return namedColors["darkgray"]; } }
+ public static Color DarkGreen { get { return namedColors["darkgreen"]; } }
+ public static Color DarkKhaki { get { return namedColors["darkkhaki"]; } }
+ public static Color DarkMagenta { get { return namedColors["darkmagenta"]; } }
+ public static Color DarkOliveGreen { get { return namedColors["darkolivegreen"]; } }
+ public static Color DarkOrange { get { return namedColors["darkorange"]; } }
+ public static Color DarkOrchid { get { return namedColors["darkorchid"]; } }
+ public static Color DarkRed { get { return namedColors["darkred"]; } }
+ public static Color DarkSalmon { get { return namedColors["darksalmon"]; } }
+ public static Color DarkSeaGreen { get { return namedColors["darkseagreen"]; } }
+ public static Color DarkSlateBlue { get { return namedColors["darkslateblue"]; } }
+ public static Color DarkSlateGray { get { return namedColors["darkslategray"]; } }
+ public static Color DarkTurquoise { get { return namedColors["darkturquoise"]; } }
+ public static Color DarkViolet { get { return namedColors["darkviolet"]; } }
+ public static Color DeepPink { get { return namedColors["deeppink"]; } }
+ public static Color DeepSkyBlue { get { return namedColors["deepskyblue"]; } }
+ public static Color DimGray { get { return namedColors["dimgray"]; } }
+ public static Color DodgerBlue { get { return namedColors["dodgerblue"]; } }
+ public static Color Firebrick { get { return namedColors["firebrick"]; } }
+ public static Color FloralWhite { get { return namedColors["floralwhite"]; } }
+ public static Color ForestGreen { get { return namedColors["forestgreen"]; } }
+ public static Color Fuchsia { get { return namedColors["fuchsia"]; } }
+ public static Color Gainsboro { get { return namedColors["gainsboro"]; } }
+ public static Color GhostWhite { get { return namedColors["ghostwhite"]; } }
+ public static Color Gold { get { return namedColors["gold"]; } }
+ public static Color Goldenrod { get { return namedColors["goldenrod"]; } }
+ public static Color Gray { get { return namedColors["gray"]; } }
+ public static Color Green { get { return namedColors["green"]; } }
+ public static Color GreenYellow { get { return namedColors["greenyellow"]; } }
+ public static Color Honeydew { get { return namedColors["honeydew"]; } }
+ public static Color HotPink { get { return namedColors["hotpink"]; } }
+ public static Color IndianRed { get { return namedColors["indianred"]; } }
+ public static Color Indigo { get { return namedColors["indigo"]; } }
+ public static Color Ivory { get { return namedColors["ivory"]; } }
+ public static Color Khaki { get { return namedColors["khaki"]; } }
+ public static Color Lavender { get { return namedColors["lavender"]; } }
+ public static Color LavenderBlush { get { return namedColors["lavenderblush"]; } }
+ public static Color LawnGreen { get { return namedColors["lawngreen"]; } }
+ public static Color LemonChiffon { get { return namedColors["lemonchiffon"]; } }
+ public static Color LightBlue { get { return namedColors["lightblue"]; } }
+ public static Color LightCoral { get { return namedColors["lightcoral"]; } }
+ public static Color LightCyan { get { return namedColors["lightcyan"]; } }
+ public static Color LightGoldenrod { get { return namedColors["lightgoldenrod"]; } }
+ public static Color LightGray { get { return namedColors["lightgray"]; } }
+ public static Color LightGreen { get { return namedColors["lightgreen"]; } }
+ public static Color LightPink { get { return namedColors["lightpink"]; } }
+ public static Color LightSalmon { get { return namedColors["lightsalmon"]; } }
+ public static Color LightSeaGreen { get { return namedColors["lightseagreen"]; } }
+ public static Color LightSkyBlue { get { return namedColors["lightskyblue"]; } }
+ public static Color LightSlateGray { get { return namedColors["lightslategray"]; } }
+ public static Color LightSteelBlue { get { return namedColors["lightsteelblue"]; } }
+ public static Color LightYellow { get { return namedColors["lightyellow"]; } }
+ public static Color Lime { get { return namedColors["lime"]; } }
+ public static Color Limegreen { get { return namedColors["limegreen"]; } }
+ public static Color Linen { get { return namedColors["linen"]; } }
+ public static Color Magenta { get { return namedColors["magenta"]; } }
+ public static Color Maroon { get { return namedColors["maroon"]; } }
+ public static Color MediumAquamarine { get { return namedColors["mediumaquamarine"]; } }
+ public static Color MediumBlue { get { return namedColors["mediumblue"]; } }
+ public static Color MediumOrchid { get { return namedColors["mediumorchid"]; } }
+ public static Color MediumPurple { get { return namedColors["mediumpurple"]; } }
+ public static Color MediumSeaGreen { get { return namedColors["mediumseagreen"]; } }
+ public static Color MediumSlateBlue { get { return namedColors["mediumslateblue"]; } }
+ public static Color MediumSpringGreen { get { return namedColors["mediumspringgreen"]; } }
+ public static Color MediumTurquoise { get { return namedColors["mediumturquoise"]; } }
+ public static Color MediumVioletRed { get { return namedColors["mediumvioletred"]; } }
+ public static Color MidnightBlue { get { return namedColors["midnightblue"]; } }
+ public static Color MintCream { get { return namedColors["mintcream"]; } }
+ public static Color MistyRose { get { return namedColors["mistyrose"]; } }
+ public static Color Moccasin { get { return namedColors["moccasin"]; } }
+ public static Color NavajoWhite { get { return namedColors["navajowhite"]; } }
+ public static Color NavyBlue { get { return namedColors["navyblue"]; } }
+ public static Color OldLace { get { return namedColors["oldlace"]; } }
+ public static Color Olive { get { return namedColors["olive"]; } }
+ public static Color OliveDrab { get { return namedColors["olivedrab"]; } }
+ public static Color Orange { get { return namedColors["orange"]; } }
+ public static Color OrangeRed { get { return namedColors["orangered"]; } }
+ public static Color Orchid { get { return namedColors["orchid"]; } }
+ public static Color PaleGoldenrod { get { return namedColors["palegoldenrod"]; } }
+ public static Color PaleGreen { get { return namedColors["palegreen"]; } }
+ public static Color PaleTurquoise { get { return namedColors["paleturquoise"]; } }
+ public static Color PaleVioletRed { get { return namedColors["palevioletred"]; } }
+ public static Color PapayaWhip { get { return namedColors["papayawhip"]; } }
+ public static Color PeachPuff { get { return namedColors["peachpuff"]; } }
+ public static Color Peru { get { return namedColors["peru"]; } }
+ public static Color Pink { get { return namedColors["pink"]; } }
+ public static Color Plum { get { return namedColors["plum"]; } }
+ public static Color PowderBlue { get { return namedColors["powderblue"]; } }
+ public static Color Purple { get { return namedColors["purple"]; } }
+ public static Color RebeccaPurple { get { return namedColors["rebeccapurple"]; } }
+ public static Color Red { get { return namedColors["red"]; } }
+ public static Color RosyBrown { get { return namedColors["rosybrown"]; } }
+ public static Color RoyalBlue { get { return namedColors["royalblue"]; } }
+ public static Color SaddleBrown { get { return namedColors["saddlebrown"]; } }
+ public static Color Salmon { get { return namedColors["salmon"]; } }
+ public static Color SandyBrown { get { return namedColors["sandybrown"]; } }
+ public static Color SeaGreen { get { return namedColors["seagreen"]; } }
+ public static Color SeaShell { get { return namedColors["seashell"]; } }
+ public static Color Sienna { get { return namedColors["sienna"]; } }
+ public static Color Silver { get { return namedColors["silver"]; } }
+ public static Color SkyBlue { get { return namedColors["skyblue"]; } }
+ public static Color SlateBlue { get { return namedColors["slateblue"]; } }
+ public static Color SlateGray { get { return namedColors["slategray"]; } }
+ public static Color Snow { get { return namedColors["snow"]; } }
+ public static Color SpringGreen { get { return namedColors["springgreen"]; } }
+ public static Color SteelBlue { get { return namedColors["steelblue"]; } }
+ public static Color Tan { get { return namedColors["tan"]; } }
+ public static Color Teal { get { return namedColors["teal"]; } }
+ public static Color Thistle { get { return namedColors["thistle"]; } }
+ public static Color Tomato { get { return namedColors["tomato"]; } }
+ public static Color Transparent { get { return namedColors["transparent"]; } }
+ public static Color Turquoise { get { return namedColors["turquoise"]; } }
+ public static Color Violet { get { return namedColors["violet"]; } }
+ public static Color WebGreen { get { return namedColors["webgreen"]; } }
+ public static Color WebGray { get { return namedColors["webgray"]; } }
+ public static Color WebMaroon { get { return namedColors["webmaroon"]; } }
+ public static Color WebPurple { get { return namedColors["webpurple"]; } }
+ public static Color Wheat { get { return namedColors["wheat"]; } }
+ public static Color White { get { return namedColors["white"]; } }
+ public static Color WhiteSmoke { get { return namedColors["whitesmoke"]; } }
+ public static Color Yellow { get { return namedColors["yellow"]; } }
+ public static Color YellowGreen { get { return namedColors["yellowgreen"]; } }
+ }
+}
diff --git a/modules/mono/glue/Managed/Files/DebuggingUtils.cs b/modules/mono/glue/Managed/Files/DebuggingUtils.cs
index b27816084e..edfe3464ec 100644
--- a/modules/mono/glue/Managed/Files/DebuggingUtils.cs
+++ b/modules/mono/glue/Managed/Files/DebuggingUtils.cs
@@ -19,6 +19,12 @@ namespace Godot
sb.Append(" ");
}
+ public static void InstallTraceListener()
+ {
+ Trace.Listeners.Clear();
+ Trace.Listeners.Add(new GodotTraceListener());
+ }
+
public static void GetStackFrameInfo(StackFrame frame, out string fileName, out int fileLineNumber, out string methodDecl)
{
fileName = frame.GetFileName();
diff --git a/modules/mono/glue/Managed/Files/Dictionary.cs b/modules/mono/glue/Managed/Files/Dictionary.cs
index 523e48c31a..6ab8549a01 100644
--- a/modules/mono/glue/Managed/Files/Dictionary.cs
+++ b/modules/mono/glue/Managed/Files/Dictionary.cs
@@ -29,9 +29,7 @@ namespace Godot.Collections
}
public class Dictionary :
- IDictionary<object, object>,
- ICollection<KeyValuePair<object, object>>,
- IEnumerable<KeyValuePair<object, object>>,
+ IDictionary,
IDisposable
{
DictionarySafeHandle safeHandle;
@@ -42,6 +40,14 @@ namespace Godot.Collections
safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor());
}
+ public Dictionary(IDictionary dictionary) : this()
+ {
+ if (dictionary == null)
+ throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'");
+
+ MarshalUtils.IDictionaryToDictionary(dictionary, GetPtr());
+ }
+
internal Dictionary(DictionarySafeHandle handle)
{
safeHandle = handle;
@@ -54,6 +60,9 @@ namespace Godot.Collections
internal IntPtr GetPtr()
{
+ if (disposed)
+ throw new ObjectDisposedException(GetType().FullName);
+
return safeHandle.DangerousGetHandle();
}
@@ -71,19 +80,9 @@ namespace Godot.Collections
disposed = true;
}
- public object this[object key]
- {
- get
- {
- return godot_icall_Dictionary_GetValue(GetPtr(), key);
- }
- set
- {
- godot_icall_Dictionary_SetValue(GetPtr(), key, value);
- }
- }
+ // IDictionary
- public ICollection<object> Keys
+ public ICollection Keys
{
get
{
@@ -92,7 +91,7 @@ namespace Godot.Collections
}
}
- public ICollection<object> Values
+ public ICollection Values
{
get
{
@@ -101,97 +100,102 @@ namespace Godot.Collections
}
}
- public int Count
- {
- get
- {
- return godot_icall_Dictionary_Count(GetPtr());
- }
- }
+ public bool IsFixedSize => false;
- public bool IsReadOnly
- {
- get
- {
- return false;
- }
- }
+ public bool IsReadOnly => false;
- public void Add(object key, object value)
+ public object this[object key]
{
- godot_icall_Dictionary_Add(GetPtr(), key, value);
+ get => godot_icall_Dictionary_GetValue(GetPtr(), key);
+ set => godot_icall_Dictionary_SetValue(GetPtr(), key, value);
}
- public void Add(KeyValuePair<object, object> item)
- {
- Add(item.Key, item.Value);
- }
+ public void Add(object key, object value) => godot_icall_Dictionary_Add(GetPtr(), key, value);
- public void Clear()
- {
- godot_icall_Dictionary_Clear(GetPtr());
- }
+ public void Clear() => godot_icall_Dictionary_Clear(GetPtr());
- public bool Contains(KeyValuePair<object, object> item)
- {
- return godot_icall_Dictionary_Contains(GetPtr(), item.Key, item.Value);
- }
+ public bool Contains(object key) => godot_icall_Dictionary_ContainsKey(GetPtr(), key);
- public bool ContainsKey(object key)
- {
- return godot_icall_Dictionary_ContainsKey(GetPtr(), key);
- }
+ public IDictionaryEnumerator GetEnumerator() => new DictionaryEnumerator(this);
+
+ public void Remove(object key) => godot_icall_Dictionary_RemoveKey(GetPtr(), key);
+
+ // ICollection
+
+ public object SyncRoot => this;
- public void CopyTo(KeyValuePair<object, object>[] array, int arrayIndex)
+ public bool IsSynchronized => false;
+
+ public int Count => godot_icall_Dictionary_Count(GetPtr());
+
+ public void CopyTo(System.Array array, int index)
{
- // TODO 3 internal calls, can reduce to 1
+ // TODO Can be done with single internal call
+
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), "Value cannot be null.");
+
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension.");
+
Array keys = (Array)Keys;
Array values = (Array)Values;
int count = Count;
+ if (array.Length < (index + count))
+ throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
+
for (int i = 0; i < count; i++)
{
- // TODO 2 internal calls, can reduce to 1
- array[arrayIndex] = new KeyValuePair<object, object>(keys[i], values[i]);
- arrayIndex++;
+ array.SetValue(new DictionaryEntry(keys[i], values[i]), index);
+ index++;
}
}
- public IEnumerator<KeyValuePair<object, object>> GetEnumerator()
+ // IEnumerable
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ private class DictionaryEnumerator : IDictionaryEnumerator
{
- // TODO 3 internal calls, can reduce to 1
- Array keys = (Array)Keys;
- Array values = (Array)Values;
- int count = Count;
+ Array keys;
+ Array values;
+ int count;
+ int index = -1;
- for (int i = 0; i < count; i++)
+ public DictionaryEnumerator(Dictionary dictionary)
{
- // TODO 2 internal calls, can reduce to 1
- yield return new KeyValuePair<object, object>(keys[i], values[i]);
+ // TODO 3 internal calls, can reduce to 1
+ keys = (Array)dictionary.Keys;
+ values = (Array)dictionary.Values;
+ count = dictionary.Count;
}
- }
- public bool Remove(object key)
- {
- return godot_icall_Dictionary_RemoveKey(GetPtr(), key);
- }
+ public object Current => Entry;
- public bool Remove(KeyValuePair<object, object> item)
- {
- return godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value);
- }
+ public DictionaryEntry Entry =>
+ // TODO 2 internal calls, can reduce to 1
+ new DictionaryEntry(keys[index], values[index]);
- public bool TryGetValue(object key, out object value)
- {
- object retValue;
- bool found = godot_icall_Dictionary_TryGetValue(GetPtr(), key, out retValue);
- value = found ? retValue : default(object);
- return found;
+ public object Key => Entry.Key;
+
+ public object Value => Entry.Value;
+
+ public bool MoveNext()
+ {
+ index++;
+ return index < count;
+ }
+
+ public void Reset()
+ {
+ index = -1;
+ }
}
- IEnumerator IEnumerable.GetEnumerator()
+ public override string ToString()
{
- return GetEnumerator();
+ return godot_icall_Dictionary_ToString(GetPtr());
}
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -204,6 +208,9 @@ namespace Godot.Collections
internal extern static object godot_icall_Dictionary_GetValue(IntPtr ptr, object key);
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -235,20 +242,52 @@ namespace Godot.Collections
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static string godot_icall_Dictionary_ToString(IntPtr ptr);
}
public class Dictionary<TKey, TValue> :
- IDictionary<TKey, TValue>,
- ICollection<KeyValuePair<TKey, TValue>>,
- IEnumerable<KeyValuePair<TKey, TValue>>
+ IDictionary<TKey, TValue>
{
Dictionary objectDict;
+ internal static int valTypeEncoding;
+ internal static IntPtr valTypeClass;
+
+ static Dictionary()
+ {
+ Dictionary.godot_icall_Dictionary_Generic_GetValueTypeInfo(typeof(TValue), out valTypeEncoding, out valTypeClass);
+ }
+
public Dictionary()
{
objectDict = new Dictionary();
}
+ public Dictionary(IDictionary<TKey, TValue> dictionary)
+ {
+ objectDict = new Dictionary();
+
+ if (dictionary == null)
+ throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'");
+
+ // TODO: Can be optimized
+
+ IntPtr godotDictionaryPtr = GetPtr();
+
+ foreach (KeyValuePair<TKey, TValue> entry in dictionary)
+ {
+ Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, entry.Key, entry.Value);
+ }
+ }
+
public Dictionary(Dictionary dictionary)
{
objectDict = dictionary;
@@ -269,11 +308,18 @@ namespace Godot.Collections
return from.objectDict;
}
+ internal IntPtr GetPtr()
+ {
+ return objectDict.GetPtr();
+ }
+
+ // IDictionary<TKey, TValue>
+
public TValue this[TKey key]
{
get
{
- return (TValue)objectDict[key];
+ return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(objectDict.GetPtr(), key, valTypeEncoding, valTypeClass);
}
set
{
@@ -299,6 +345,31 @@ namespace Godot.Collections
}
}
+ public void Add(TKey key, TValue value)
+ {
+ objectDict.Add(key, value);
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return objectDict.Contains(key);
+ }
+
+ public bool Remove(TKey key)
+ {
+ return Dictionary.godot_icall_Dictionary_RemoveKey(GetPtr(), key);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ object retValue;
+ bool found = Dictionary.godot_icall_Dictionary_TryGetValue_Generic(GetPtr(), key, out retValue, valTypeEncoding, valTypeClass);
+ value = found ? (TValue)retValue : default(TValue);
+ return found;
+ }
+
+ // ICollection<KeyValuePair<TKey, TValue>>
+
public int Count
{
get
@@ -315,11 +386,6 @@ namespace Godot.Collections
}
}
- public void Add(TKey key, TValue value)
- {
- objectDict.Add(key, value);
- }
-
public void Add(KeyValuePair<TKey, TValue> item)
{
objectDict.Add(item.Key, item.Value);
@@ -335,18 +401,22 @@ namespace Godot.Collections
return objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value));
}
- public bool ContainsKey(TKey key)
- {
- return objectDict.ContainsKey(key);
- }
-
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), "Value cannot be null.");
+
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension.");
+
// TODO 3 internal calls, can reduce to 1
Array<TKey> keys = (Array<TKey>)Keys;
Array<TValue> values = (Array<TValue>)Values;
int count = Count;
+ if (array.Length < (arrayIndex + count))
+ throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
+
for (int i = 0; i < count; i++)
{
// TODO 2 internal calls, can reduce to 1
@@ -355,6 +425,13 @@ namespace Godot.Collections
}
}
+ public bool Remove(KeyValuePair<TKey, TValue> item)
+ {
+ return Dictionary.godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value); ;
+ }
+
+ // IEnumerable<KeyValuePair<TKey, TValue>>
+
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
// TODO 3 internal calls, can reduce to 1
@@ -369,32 +446,11 @@ namespace Godot.Collections
}
}
- public bool Remove(TKey key)
- {
- return objectDict.Remove(key);
- }
-
- public bool Remove(KeyValuePair<TKey, TValue> item)
- {
- return objectDict.Remove(new KeyValuePair<object, object>(item.Key, item.Value));
- }
-
- public bool TryGetValue(TKey key, out TValue value)
- {
- object retValue;
- bool found = objectDict.TryGetValue(key, out retValue);
- value = found ? (TValue)retValue : default(TValue);
- return found;
- }
-
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
- internal IntPtr GetPtr()
- {
- return objectDict.GetPtr();
- }
+ public override string ToString() => objectDict.ToString();
}
}
diff --git a/modules/mono/glue/Managed/Files/Dispatcher.cs b/modules/mono/glue/Managed/Files/Dispatcher.cs
new file mode 100644
index 0000000000..072e0f20ff
--- /dev/null
+++ b/modules/mono/glue/Managed/Files/Dispatcher.cs
@@ -0,0 +1,13 @@
+using System.Runtime.CompilerServices;
+
+namespace Godot
+{
+ public static class Dispatcher
+ {
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern GodotTaskScheduler godot_icall_DefaultGodotTaskScheduler();
+
+ public static GodotSynchronizationContext SynchronizationContext =>
+ godot_icall_DefaultGodotTaskScheduler().Context;
+ }
+}
diff --git a/modules/mono/glue/Managed/Files/DynamicObject.cs b/modules/mono/glue/Managed/Files/DynamicObject.cs
new file mode 100644
index 0000000000..a0f105d55e
--- /dev/null
+++ b/modules/mono/glue/Managed/Files/DynamicObject.cs
@@ -0,0 +1,213 @@
+
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq.Expressions;
+using System.Runtime.CompilerServices;
+
+namespace Godot
+{
+ /// <summary>
+ /// Represents an <see cref="Godot.Object"/> whose members can be dynamically accessed at runtime through the Variant API.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// The <see cref="Godot.DynamicGodotObject"/> class enables access to the Variant
+ /// members of a <see cref="Godot.Object"/> instance at runtime.
+ /// </para>
+ /// <para>
+ /// This allows accessing the class members using their original names in the engine as well as the members from the
+ /// script attached to the <see cref="Godot.Object"/>, regardless of the scripting language it was written in.
+ /// </para>
+ /// </remarks>
+ /// <example>
+ /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Godot.Object"/>.
+ /// <code>
+ /// dynamic sprite = GetNode("Sprite").DynamicGodotObject;
+ /// sprite.add_child(this);
+ ///
+ /// if ((sprite.hframes * sprite.vframes) &gt; 0)
+ /// sprite.frame = 0;
+ /// </code>
+ /// </example>
+ /// <example>
+ /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Godot.Object"/>.
+ /// <code>
+ /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject;
+ ///
+ /// if (childNode.print_allowed)
+ /// {
+ /// childNode.message = "Hello from C#";
+ /// childNode.print_message(3);
+ /// }
+ /// </code>
+ /// The <c>ChildNode</c> node has the following GDScript script attached:
+ /// <code>
+ /// // # ChildNode.gd
+ /// // var print_allowed = true
+ /// // var message = ""
+ /// //
+ /// // func print_message(times):
+ /// // for i in times:
+ /// // print(message)
+ /// </code>
+ /// </example>
+ public class DynamicGodotObject : DynamicObject
+ {
+ /// <summary>
+ /// Gets the <see cref="Godot.Object"/> associated with this <see cref="Godot.DynamicGodotObject"/>.
+ /// </summary>
+ public Object Value { get; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Godot.DynamicGodotObject"/> class.
+ /// </summary>
+ /// <param name="godotObject">
+ /// The <see cref="Godot.Object"/> that will be associated with this <see cref="Godot.DynamicGodotObject"/>.
+ /// </param>
+ /// <exception cref="System.ArgumentNullException">
+ /// Thrown when the <paramref name="godotObject"/> parameter is null.
+ /// </exception>
+ public DynamicGodotObject(Object godotObject)
+ {
+ if (godotObject == null)
+ throw new ArgumentNullException(nameof(godotObject));
+
+ this.Value = godotObject;
+ }
+
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value));
+ }
+
+ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
+ {
+ switch (binder.Operation)
+ {
+ case ExpressionType.Equal:
+ case ExpressionType.NotEqual:
+ if (binder.ReturnType == typeof(bool) || binder.ReturnType.IsAssignableFrom(typeof(bool)))
+ {
+ if (arg == null)
+ {
+ bool boolResult = Object.IsInstanceValid(Value);
+
+ if (binder.Operation == ExpressionType.Equal)
+ boolResult = !boolResult;
+
+ result = boolResult;
+ return true;
+ }
+
+ if (arg is Object other)
+ {
+ bool boolResult = (Value == other);
+
+ if (binder.Operation == ExpressionType.NotEqual)
+ boolResult = !boolResult;
+
+ result = boolResult;
+ return true;
+ }
+ }
+
+ break;
+ default:
+ // We're not implementing operators <, <=, >, and >= (LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual).
+ // These are used on the actual pointers in variant_op.cpp. It's better to let the user do that explicitly.
+ break;
+ }
+
+ return base.TryBinaryOperation(binder, arg, out result);
+ }
+
+ public override bool TryConvert(ConvertBinder binder, out object result)
+ {
+ if (binder.Type == typeof(Object))
+ {
+ result = Value;
+ return true;
+ }
+
+ if (typeof(Object).IsAssignableFrom(binder.Type))
+ {
+ // Throws InvalidCastException when the cast fails
+ result = Convert.ChangeType(Value, binder.Type);
+ return true;
+ }
+
+ return base.TryConvert(binder, out result);
+ }
+
+ public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
+ {
+ if (indexes.Length == 1)
+ {
+ if (indexes[0] is string name)
+ {
+ return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), name, out result);
+ }
+ }
+
+ return base.TryGetIndex(binder, indexes, out result);
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result);
+ }
+
+ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
+ {
+ return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result);
+ }
+
+ public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
+ {
+ if (indexes.Length == 1)
+ {
+ if (indexes[0] is string name)
+ {
+ return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), name, value);
+ }
+ }
+
+ return base.TrySetIndex(binder, indexes, value);
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value);
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value);
+
+ #region We don't override these methods
+
+ // Looks like this is not usable from C#
+ //public override bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result);
+
+ // Object members cannot be deleted
+ //public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);
+ //public override bool TryDeleteMember(DeleteMemberBinder binder);
+
+ // 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
+ //public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result);
+
+ #endregion
+ }
+}
diff --git a/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs
index 71534d7782..5023725f17 100644
--- a/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs
+++ b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs
@@ -2,42 +2,42 @@ namespace Godot
{
public partial class Node
{
- public T GetNode<T>(NodePath path) where T : Godot.Node
+ public T GetNode<T>(NodePath path) where T : class
{
- return (T)GetNode(path);
+ return (T)(object)GetNode(path);
}
- public T GetNodeOrNull<T>(NodePath path) where T : Godot.Node
+ public T GetNodeOrNull<T>(NodePath path) where T : class
{
return GetNode(path) as T;
}
- public T GetChild<T>(int idx) where T : Godot.Node
+ public T GetChild<T>(int idx) where T : class
{
- return (T)GetChild(idx);
+ return (T)(object)GetChild(idx);
}
- public T GetChildOrNull<T>(int idx) where T : Godot.Node
+ public T GetChildOrNull<T>(int idx) where T : class
{
return GetChild(idx) as T;
}
- public T GetOwner<T>() where T : Godot.Node
+ public T GetOwner<T>() where T : class
{
- return (T)GetOwner();
+ return (T)(object)Owner;
}
- public T GetOwnerOrNull<T>() where T : Godot.Node
+ public T GetOwnerOrNull<T>() where T : class
{
- return GetOwner() as T;
+ return Owner as T;
}
- public T GetParent<T>() where T : Godot.Node
+ public T GetParent<T>() where T : class
{
- return (T)GetParent();
+ return (T)(object)GetParent();
}
- public T GetParentOrNull<T>() where T : Godot.Node
+ public T GetParentOrNull<T>() where T : class
{
return GetParent() as T;
}
diff --git a/modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs b/modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs
index ceecc589e6..684d160b57 100644
--- a/modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs
+++ b/modules/mono/glue/Managed/Files/Extensions/ResourceLoaderExtensions.cs
@@ -2,9 +2,9 @@ namespace Godot
{
public static partial class ResourceLoader
{
- public static T Load<T>(string path) where T : Godot.Resource
+ public static T Load<T>(string path) where T : class
{
- return (T) Load(path);
+ return (T)(object)Load(path);
}
}
}
diff --git a/modules/mono/glue/Managed/Files/GD.cs b/modules/mono/glue/Managed/Files/GD.cs
index 264be23588..2068099ac6 100644
--- a/modules/mono/glue/Managed/Files/GD.cs
+++ b/modules/mono/glue/Managed/Files/GD.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Runtime.CompilerServices;
#if REAL_T_IS_DOUBLE
using real_t = System.Double;
@@ -12,12 +13,12 @@ namespace Godot
{
public static partial class GD
{
- public static object Bytes2Var(byte[] bytes)
+ public static object Bytes2Var(byte[] bytes, bool allow_objects = false)
{
- return godot_icall_GD_bytes2var(bytes);
+ return godot_icall_GD_bytes2var(bytes, allow_objects);
}
- public static object Convert(object what, int type)
+ public static object Convert(object what, Variant.Type type)
{
return godot_icall_GD_convert(what, type);
}
@@ -50,7 +51,7 @@ namespace Godot
return godot_icall_GD_hash(var);
}
- public static Object InstanceFromId(int instanceId)
+ public static Object InstanceFromId(ulong instanceId)
{
return godot_icall_GD_instance_from_id(instanceId);
}
@@ -65,14 +66,24 @@ namespace Godot
return ResourceLoader.Load(path);
}
- public static T Load<T>(string path) where T : Godot.Resource
+ public static T Load<T>(string path) where T : class
{
- return (T) ResourceLoader.Load(path);
+ return ResourceLoader.Load<T>(path);
+ }
+
+ public static void PushError(string message)
+ {
+ godot_icall_GD_pusherror(message);
+ }
+
+ public static void PushWarning(string message)
+ {
+ godot_icall_GD_pushwarning(message);
}
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()
@@ -82,89 +93,80 @@ 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 int[] Range(int length)
+ public static float Randf()
{
- var ret = new int[length];
-
- for (int i = 0; i < length; i++)
- {
- ret[i] = i;
- }
-
- return ret;
+ return godot_icall_GD_randf();
}
- public static int[] Range(int from, int to)
+ public static uint Randi()
{
- if (to < from)
- return new int[0];
+ return godot_icall_GD_randi();
+ }
- var ret = new int[to - from];
+ public static void Randomize()
+ {
+ godot_icall_GD_randomize();
+ }
- for (int i = from; i < to; i++)
- {
- ret[i - from] = i;
- }
+ public static double RandRange(double from, double to)
+ {
+ return godot_icall_GD_rand_range(from, to);
+ }
- return ret;
+ public static uint RandSeed(ulong seed, out ulong newSeed)
+ {
+ return godot_icall_GD_rand_seed(seed, out newSeed);
}
- public static int[] Range(int from, int to, int increment)
+ public static IEnumerable<int> Range(int end)
{
- if (to < from && increment > 0)
- return new int[0];
- if (to > from && increment < 0)
- return new int[0];
+ return Range(0, end, 1);
+ }
- // Calculate count
- int count;
+ public static IEnumerable<int> Range(int start, int end)
+ {
+ return Range(start, end, 1);
+ }
- if (increment > 0)
- count = (to - from - 1) / increment + 1;
- else
- count = (from - to - 1) / -increment + 1;
+ public static IEnumerable<int> Range(int start, int end, int step)
+ {
+ if (end < start && step > 0)
+ yield break;
- var ret = new int[count];
+ if (end > start && step < 0)
+ yield break;
- if (increment > 0)
+ if (step > 0)
{
- int idx = 0;
- for (int i = from; i < to; i += increment)
- {
- ret[idx++] = i;
- }
+ for (int i = start; i < end; i += step)
+ yield return i;
}
else
{
- int idx = 0;
- for (int i = from; i > to; i += increment)
- {
- ret[idx++] = i;
- }
+ for (int i = start; i > end; i += step)
+ yield return i;
}
-
- return ret;
}
- public static void Seed(int seed)
+ public static void Seed(ulong seed)
{
godot_icall_GD_seed(seed);
}
@@ -184,9 +186,9 @@ namespace Godot
return godot_icall_GD_type_exists(type);
}
- public static byte[] Var2Bytes(object var)
+ public static byte[] Var2Bytes(object var, bool full_objects = false)
{
- return godot_icall_GD_var2bytes(var);
+ return godot_icall_GD_var2bytes(var, full_objects);
}
public static string Var2Str(object var)
@@ -195,16 +197,16 @@ namespace Godot
}
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static object godot_icall_GD_bytes2var(byte[] bytes);
+ internal extern static object godot_icall_GD_bytes2var(byte[] bytes, bool allow_objects);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static object godot_icall_GD_convert(object what, int type);
+ internal extern static object godot_icall_GD_convert(object what, Variant.Type type);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static int godot_icall_GD_hash(object var);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static Object godot_icall_GD_instance_from_id(int instance_id);
+ internal extern static Object godot_icall_GD_instance_from_id(ulong instance_id);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_GD_print(object[] what);
@@ -222,7 +224,23 @@ namespace Godot
internal extern static void godot_icall_GD_printt(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static void godot_icall_GD_seed(int seed);
+ internal extern static float godot_icall_GD_randf();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static uint godot_icall_GD_randi();
+
+ [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);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_GD_seed(ulong seed);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static string godot_icall_GD_str(object[] what);
@@ -234,9 +252,15 @@ namespace Godot
internal extern static bool godot_icall_GD_type_exists(string type);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static byte[] godot_icall_GD_var2bytes(object what);
+ internal extern static byte[] godot_icall_GD_var2bytes(object what, bool full_objects);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static string godot_icall_GD_var2str(object var);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_GD_pusherror(string type);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_GD_pushwarning(string type);
}
}
diff --git a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs
index e727781d63..4b5e3f8761 100644
--- a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs
+++ b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs
@@ -6,17 +6,16 @@ namespace Godot
{
public class GodotSynchronizationContext : SynchronizationContext
{
- private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
+ private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
public override void Post(SendOrPostCallback d, object state)
{
- queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
+ _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
}
public void ExecutePendingContinuations()
{
- KeyValuePair<SendOrPostCallback, object> workItem;
- while (queue.TryTake(out workItem))
+ while (_queue.TryTake(out var workItem))
{
workItem.Key(workItem.Value);
}
diff --git a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs
index 9a40fef5a9..8eaeea50dc 100644
--- a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs
+++ b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs
@@ -8,7 +8,7 @@ namespace Godot
{
public class GodotTaskScheduler : TaskScheduler
{
- private GodotSynchronizationContext Context { get; set; }
+ internal GodotSynchronizationContext Context { get; }
private readonly LinkedList<Task> _tasks = new LinkedList<Task>();
public GodotTaskScheduler()
@@ -28,14 +28,10 @@ namespace Godot
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
if (SynchronizationContext.Current != Context)
- {
return false;
- }
if (taskWasPreviouslyQueued)
- {
TryDequeue(task);
- }
return TryExecuteTask(task);
}
@@ -52,7 +48,8 @@ namespace Godot
{
lock (_tasks)
{
- return _tasks.ToArray();
+ foreach (Task task in _tasks)
+ yield return task;
}
}
diff --git a/modules/mono/glue/Managed/Files/GodotTraceListener.cs b/modules/mono/glue/Managed/Files/GodotTraceListener.cs
new file mode 100644
index 0000000000..f1a00ae0fa
--- /dev/null
+++ b/modules/mono/glue/Managed/Files/GodotTraceListener.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Diagnostics;
+
+namespace Godot
+{
+ internal class GodotTraceListener : TraceListener
+ {
+ public override void Write(string message)
+ {
+ GD.PrintRaw(message);
+ }
+
+ public override void WriteLine(string message)
+ {
+ GD.Print(message);
+ }
+
+ public override void Fail(string message, string detailMessage)
+ {
+ GD.PrintErr("Assertion failed: ", message);
+ if (detailMessage != null)
+ {
+ GD.PrintErr(" Details: ", detailMessage);
+ }
+
+ try
+ {
+ var stackTrace = new StackTrace(true).ToString();
+ GD.PrintErr(stackTrace);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ }
+}
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 f7699a15bf..a1d63a62ef 100644
--- a/modules/mono/glue/Managed/Files/MarshalUtils.cs
+++ b/modules/mono/glue/Managed/Files/MarshalUtils.cs
@@ -1,18 +1,212 @@
using System;
-using Godot.Collections;
+using System.Collections;
+using System.Collections.Generic;
namespace Godot
{
+ using Array = Godot.Collections.Array;
+ using Dictionary = Godot.Collections.Dictionary;
+
static class MarshalUtils
{
- static bool IsArrayGenericType(Type type)
+ /// <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(Array<>);
+ return type.GetGenericTypeDefinition() == typeof(Godot.Collections.Dictionary<,>);
}
- static bool IsDictionaryGenericType(Type type)
+ static void ArrayGetElementType(Type arrayType, out Type elementType)
{
- return type.GetGenericTypeDefinition() == typeof(Dictionary<, >);
+ elementType = arrayType.GetGenericArguments()[0];
+ }
+
+ static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType)
+ {
+ 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
+
+ internal static void EnumerableToArray(IEnumerable enumerable, IntPtr godotArrayPtr)
+ {
+ if (enumerable is ICollection collection)
+ {
+ int count = collection.Count;
+
+ object[] tempArray = new object[count];
+ collection.CopyTo(tempArray, 0);
+
+ for (int i = 0; i < count; i++)
+ {
+ Array.godot_icall_Array_Add(godotArrayPtr, tempArray[i]);
+ }
+ }
+ else
+ {
+ foreach (object element in enumerable)
+ {
+ Array.godot_icall_Array_Add(godotArrayPtr, element);
+ }
+ }
+ }
+
+ internal static void IDictionaryToDictionary(IDictionary dictionary, IntPtr godotDictionaryPtr)
+ {
+ foreach (DictionaryEntry entry in dictionary)
+ {
+ Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, entry.Key, entry.Value);
+ }
+ }
+
+ internal static void GenericIDictionaryToDictionary(object dictionary, IntPtr godotDictionaryPtr)
+ {
+#if DEBUG
+ if (!GenericIDictionaryIsAssignableFromType(dictionary.GetType()))
+ throw new InvalidOperationException("The type does not implement IDictionary<,>");
+#endif
+
+ // TODO: Can we optimize this?
+
+ var keys = ((IEnumerable)dictionary.GetType().GetProperty("Keys").GetValue(dictionary)).GetEnumerator();
+ var values = ((IEnumerable)dictionary.GetType().GetProperty("Values").GetValue(dictionary)).GetEnumerator();
+
+ while (keys.MoveNext() && values.MoveNext())
+ {
+ object key = keys.Current;
+ object value = values.Current;
+
+ Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, key, value);
+ }
}
}
}
diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs
index a89dfe5f27..ce34cd6a99 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,20 @@ namespace Godot
return (weight - from) / (to - from);
}
+ public static bool IsEqualApprox(real_t a, real_t b)
+ {
+ // Check for exact equality first, required to handle "infinity" values.
+ if (a == b) {
+ return true;
+ }
+ // Then check for approximate equality.
+ 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,11 +180,22 @@ 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;
}
+ public static real_t LerpAngle(real_t from, real_t to, real_t weight) {
+ real_t difference = (to - from) % Mathf.Tau;
+ real_t distance = ((2 * difference) % Mathf.Tau) - difference;
+ return from + distance * weight;
+ }
+
public static real_t Log(real_t s)
{
return (real_t)Math.Log(s);
@@ -183,6 +221,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--;
@@ -206,7 +249,7 @@ namespace Godot
public static real_t PosMod(real_t a, real_t b)
{
real_t c = a % b;
- if ((c < 0 && b > 0) || (c > 0 && b < 0))
+ if ((c < 0 && b > 0) || (c > 0 && b < 0))
{
c += b;
}
@@ -219,7 +262,7 @@ namespace Godot
public static int PosMod(int a, int b)
{
int c = a % b;
- if ((c < 0 && b > 0) || (c > 0 && b < 0))
+ if ((c < 0 && b > 0) || (c > 0 && b < 0))
{
c += b;
}
@@ -261,6 +304,16 @@ namespace Godot
return (real_t)Math.Sinh(s);
}
+ public static real_t SmoothStep(real_t from, real_t to, real_t weight)
+ {
+ if (IsEqualApprox(from, to))
+ {
+ return from;
+ }
+ real_t x = Clamp((weight - from) / (to - from), (real_t)0.0, (real_t)1.0);
+ return x * x * (3 - 2 * x);
+ }
+
public static real_t Sqrt(real_t s)
{
return (real_t)Math.Sqrt(s);
@@ -288,14 +341,14 @@ namespace Godot
public static int Wrap(int value, int min, int max)
{
- int rng = max - min;
- return min + ((value - min) % rng + rng) % rng;
+ int range = max - min;
+ return range == 0 ? min : min + ((value - min) % range + range) % range;
}
public static real_t Wrap(real_t value, real_t min, real_t max)
{
- real_t rng = max - min;
- return min + ((value - min) % rng + rng) % rng;
+ real_t range = max - min;
+ return IsZeroApprox(range) ? min : min + ((value - min) % range + range) % range;
}
}
}
diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs
index 739b7fb568..6cffc7f01d 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);
@@ -29,11 +39,21 @@ namespace Godot
public static int FloorToInt(real_t s)
{
return (int)Math.Floor(s);
- }
+ }
public static int RoundToInt(real_t s)
{
return (int)Math.Round(s);
}
+
+ public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance)
+ {
+ // Check for exact equality first, required to handle "infinity" values.
+ if (a == b) {
+ return true;
+ }
+ // Then check for approximate equality.
+ return Abs(a - b) < tolerance;
+ }
}
-} \ No newline at end of file
+}
diff --git a/modules/mono/glue/Managed/Files/NodePath.cs b/modules/mono/glue/Managed/Files/NodePath.cs
index 2c89bec87f..4de4e1e6a9 100644
--- a/modules/mono/glue/Managed/Files/NodePath.cs
+++ b/modules/mono/glue/Managed/Files/NodePath.cs
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
namespace Godot
{
- public partial class NodePath : IDisposable
+ public sealed partial class NodePath : IDisposable
{
private bool disposed = false;
@@ -11,7 +11,13 @@ namespace Godot
internal static IntPtr GetPtr(NodePath instance)
{
- return instance == null ? IntPtr.Zero : instance.ptr;
+ if (instance == null)
+ return IntPtr.Zero;
+
+ if (instance.disposed)
+ throw new ObjectDisposedException(instance.GetType().FullName);
+
+ return instance.ptr;
}
~NodePath()
@@ -25,7 +31,7 @@ namespace Godot
GC.SuppressFinalize(this);
}
- protected virtual void Dispose(bool disposing)
+ private void Dispose(bool disposing)
{
if (disposed)
return;
diff --git a/modules/mono/glue/Managed/Files/Object.base.cs b/modules/mono/glue/Managed/Files/Object.base.cs
index 30490a715f..de80f7fddc 100644
--- a/modules/mono/glue/Managed/Files/Object.base.cs
+++ b/modules/mono/glue/Managed/Files/Object.base.cs
@@ -30,7 +30,13 @@ namespace Godot
internal static IntPtr GetPtr(Object instance)
{
- return instance == null ? IntPtr.Zero : instance.ptr;
+ if (instance == null)
+ return IntPtr.Zero;
+
+ if (instance.disposed)
+ throw new ObjectDisposedException(instance.GetType().FullName);
+
+ return instance.ptr;
}
~Object()
@@ -67,11 +73,44 @@ namespace Godot
disposed = true;
}
+ public override string ToString()
+ {
+ return godot_icall_Object_ToString(GetPtr(this));
+ }
+
+ /// <summary>
+ /// Returns a new <see cref="Godot.SignalAwaiter"/> awaiter configured to complete when the instance
+ /// <paramref name="source"/> emits the signal specified by the <paramref name="signal"/> parameter.
+ /// </summary>
+ /// <param name="source">
+ /// The instance the awaiter will be listening to.
+ /// </param>
+ /// <param name="signal">
+ /// The signal the awaiter will be waiting for.
+ /// </param>
+ /// <example>
+ /// This sample prints a message once every frame up to 100 times.
+ /// <code>
+ /// public override void _Ready()
+ /// {
+ /// for (int i = 0; i &lt; 100; i++)
+ /// {
+ /// await ToSignal(GetTree(), "idle_frame");
+ /// GD.Print($"Frame {i}");
+ /// }
+ /// }
+ /// </code>
+ /// </example>
public SignalAwaiter ToSignal(Object source, string signal)
{
return new SignalAwaiter(source, signal, this);
}
+ /// <summary>
+ /// Gets a new <see cref="Godot.DynamicGodotObject"/> associated with this instance.
+ /// </summary>
+ public dynamic DynamicObject => new DynamicGodotObject(this);
+
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static IntPtr godot_icall_Object_Ctor(Object obj);
@@ -81,6 +120,9 @@ namespace Godot
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Reference_Disposed(Object obj, IntPtr ptr, bool isFinalizer);
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static string godot_icall_Object_ToString(IntPtr ptr);
+
// Used by the generated API
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static IntPtr godot_icall_Object_ClassDB_get_method(string type, string method);
diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs
index 9611dce11e..a13161d2e6 100644
--- a/modules/mono/glue/Managed/Files/Plane.cs
+++ b/modules/mono/glue/Managed/Files/Plane.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
@@ -7,6 +8,8 @@ using real_t = System.Single;
namespace Godot
{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
public struct Plane : IEquatable<Plane>
{
private Vector3 _normal;
@@ -144,7 +147,7 @@ namespace Godot
{
return point - _normal * DistanceTo(point);
}
-
+
// Constants
private static readonly Plane _planeYZ = new Plane(1, 0, 0, 0);
private static readonly Plane _planeXZ = new Plane(0, 1, 0, 0);
@@ -153,8 +156,8 @@ namespace Godot
public static Plane PlaneYZ { get { return _planeYZ; } }
public static Plane PlaneXZ { get { return _planeXZ; } }
public static Plane PlaneXY { get { return _planeXY; } }
-
- // Constructors
+
+ // Constructors
public Plane(real_t a, real_t b, real_t c, real_t d)
{
_normal = new Vector3(a, b, c);
@@ -200,7 +203,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 eaa027eb69..845c7c730e 100644
--- a/modules/mono/glue/Managed/Files/Quat.cs
+++ b/modules/mono/glue/Managed/Files/Quat.cs
@@ -8,6 +8,7 @@ using real_t = System.Single;
namespace Godot
{
+ [Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Quat : IEquatable<Quat>
{
@@ -95,6 +96,7 @@ namespace Godot
return this / Length;
}
+ [Obsolete("Set is deprecated. Use the Quat(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
public void Set(real_t x, real_t y, real_t z, real_t w)
{
this.x = x;
@@ -103,16 +105,19 @@ namespace Godot
this.w = w;
}
+ [Obsolete("Set is deprecated. Use the Quat(" + nameof(Quat) + ") constructor instead.", error: true)]
public void Set(Quat q)
{
this = q;
}
+ [Obsolete("SetAxisAngle is deprecated. Use the Quat(" + nameof(Vector3) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
public void SetAxisAngle(Vector3 axis, real_t angle)
{
this = new Quat(axis, angle);
}
+ [Obsolete("SetEuler is deprecated. Use the Quat(" + nameof(Vector3) + ") constructor instead.", error: true)]
public void SetEuler(Vector3 eulerYXZ)
{
this = new Quat(eulerYXZ);
@@ -123,22 +128,23 @@ namespace Godot
// Calculate cosine
real_t cosom = x * b.x + y * b.y + z * b.z + w * b.w;
- var to1 = new real_t[4];
+ var to1 = new Quat();
// Adjust signs if necessary
if (cosom < 0.0)
{
- cosom = -cosom; to1[0] = -b.x;
- to1[1] = -b.y;
- to1[2] = -b.z;
- to1[3] = -b.w;
+ cosom = -cosom;
+ to1.x = -b.x;
+ to1.y = -b.y;
+ to1.z = -b.z;
+ to1.w = -b.w;
}
else
{
- to1[0] = b.x;
- to1[1] = b.y;
- to1[2] = b.z;
- to1[3] = b.w;
+ to1.x = b.x;
+ to1.y = b.y;
+ to1.z = b.z;
+ to1.w = b.w;
}
real_t sinom, scale0, scale1;
@@ -162,10 +168,10 @@ namespace Godot
// Calculate final values
return new Quat
(
- scale0 * x + scale1 * to1[0],
- scale0 * y + scale1 * to1[1],
- scale0 * z + scale1 * to1[2],
- scale0 * w + scale1 * to1[3]
+ scale0 * x + scale1 * to1.x,
+ scale0 * y + scale1 * to1.y,
+ scale0 * z + scale1 * to1.z,
+ scale0 * w + scale1 * to1.w
);
}
@@ -202,7 +208,7 @@ namespace Godot
// Static Readonly Properties
public static Quat Identity { get; } = new Quat(0f, 0f, 0f, 1f);
- // Constructors
+ // Constructors
public Quat(real_t x, real_t y, real_t z, real_t w)
{
this.x = x;
@@ -228,9 +234,9 @@ namespace Godot
public Quat(Vector3 eulerYXZ)
{
- real_t half_a1 = eulerYXZ.y * (real_t)0.5;
- real_t half_a2 = eulerYXZ.x * (real_t)0.5;
- real_t half_a3 = eulerYXZ.z * (real_t)0.5;
+ real_t half_a1 = eulerYXZ.y * 0.5f;
+ real_t half_a2 = eulerYXZ.x * 0.5f;
+ real_t half_a3 = eulerYXZ.z * 0.5f;
// R = Y(a1).X(a2).Z(a3) convention for Euler angles.
// Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
@@ -245,7 +251,7 @@ namespace Godot
x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3;
y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3;
- z = -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3;
+ z = cos_a1 * cos_a2 * sin_a3 - sin_a1 * sin_a2 * cos_a3;
w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3;
}
@@ -347,9 +353,9 @@ namespace Godot
public override bool Equals(object obj)
{
- if (obj is Vector2)
+ if (obj is Quat)
{
- return Equals((Vector2)obj);
+ return Equals((Quat)obj);
}
return false;
@@ -357,7 +363,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/RID.cs b/modules/mono/glue/Managed/Files/RID.cs
index b862b8cac0..12064beca2 100644
--- a/modules/mono/glue/Managed/Files/RID.cs
+++ b/modules/mono/glue/Managed/Files/RID.cs
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
namespace Godot
{
- public partial class RID : IDisposable
+ public sealed partial class RID : IDisposable
{
private bool disposed = false;
@@ -11,7 +11,13 @@ namespace Godot
internal static IntPtr GetPtr(RID instance)
{
- return instance == null ? IntPtr.Zero : instance.ptr;
+ if (instance == null)
+ return IntPtr.Zero;
+
+ if (instance.disposed)
+ throw new ObjectDisposedException(instance.GetType().FullName);
+
+ return instance.ptr;
}
~RID()
@@ -25,7 +31,7 @@ namespace Godot
GC.SuppressFinalize(this);
}
- protected virtual void Dispose(bool disposing)
+ private void Dispose(bool disposing)
{
if (disposed)
return;
@@ -64,6 +70,8 @@ namespace Godot
return godot_icall_RID_get_id(RID.GetPtr(this));
}
+ public override string ToString() => "[RID]";
+
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static IntPtr godot_icall_RID_Ctor(IntPtr from);
diff --git a/modules/mono/glue/Managed/Files/Rect2.cs b/modules/mono/glue/Managed/Files/Rect2.cs
index cb25c267bc..99542d0c0a 100644
--- a/modules/mono/glue/Managed/Files/Rect2.cs
+++ b/modules/mono/glue/Managed/Files/Rect2.cs
@@ -8,6 +8,7 @@ using real_t = System.Single;
namespace Godot
{
+ [Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Rect2 : IEquatable<Rect2>
{
@@ -156,13 +157,13 @@ namespace Godot
public bool Intersects(Rect2 b)
{
- if (_position.x > b._position.x + b._size.x)
+ if (_position.x >= b._position.x + b._size.x)
return false;
- if (_position.x + _size.x < b._position.x)
+ if (_position.x + _size.x <= b._position.x)
return false;
- if (_position.y > b._position.y + b._size.y)
+ if (_position.y >= b._position.y + b._size.y)
return false;
- if (_position.y + _size.y < b._position.y)
+ if (_position.y + _size.y <= b._position.y)
return false;
return true;
@@ -182,8 +183,8 @@ namespace Godot
return newRect;
}
-
- // Constructors
+
+ // Constructors
public Rect2(Vector2 position, Vector2 size)
{
_position = position;
diff --git a/modules/mono/glue/Managed/Files/StringExtensions.cs b/modules/mono/glue/Managed/Files/StringExtensions.cs
index 21c9be98c1..6045c83e95 100644
--- a/modules/mono/glue/Managed/Files/StringExtensions.cs
+++ b/modules/mono/glue/Managed/Files/StringExtensions.cs
@@ -98,6 +98,66 @@ namespace Godot
}
// <summary>
+ // Return the amount of substrings in string.
+ // </summary>
+ public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0)
+ {
+ if (what.Length == 0)
+ {
+ return 0;
+ }
+
+ int len = instance.Length;
+ int slen = what.Length;
+
+ if (len < slen)
+ {
+ return 0;
+ }
+
+ string str;
+
+ if (from >= 0 && to >= 0)
+ {
+ if (to == 0)
+ {
+ to = len;
+ }
+ else if (from >= to)
+ {
+ return 0;
+ }
+ if (from == 0 && to == len)
+ {
+ str = instance;
+ }
+ else
+ {
+ str = instance.Substring(from, to - from);
+ }
+ }
+ else
+ {
+ return 0;
+ }
+
+ int c = 0;
+ int idx;
+
+ do
+ {
+ idx = str.IndexOf(what, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
+ if (idx != -1)
+ {
+ str = str.Substring(idx + slen);
+ ++c;
+ }
+ } while (idx != -1);
+
+ return c;
+ }
+
+ // <summary>
// Return a copy of the string with special characters escaped using the C language standard.
// </summary>
public static string CEscape(this string instance)
@@ -185,7 +245,7 @@ namespace Godot
int instanceIndex = 0;
int toIndex = 0;
-
+
if (caseSensitive) // Outside while loop to avoid checking multiple times, despite some code duplication.
{
while (true)
@@ -299,14 +359,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 +393,7 @@ namespace Godot
if (sep == -1)
return instance;
- return instance.Substring(sep + 1, instance.Length);
+ return instance.Substring(sep + 1);
}
// <summary>
@@ -480,9 +540,9 @@ namespace Godot
return false; // Don't start with number plz
}
- bool validChar = instance[i] >= '0' &&
- instance[i] <= '9' || instance[i] >= 'a' &&
- instance[i] <= 'z' || instance[i] >= 'A' &&
+ bool validChar = instance[i] >= '0' &&
+ instance[i] <= '9' || instance[i] >= 'a' &&
+ instance[i] <= 'z' || instance[i] >= 'A' &&
instance[i] <= 'Z' || instance[i] == '_';
if (!validChar)
@@ -911,7 +971,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/Transform.cs b/modules/mono/glue/Managed/Files/Transform.cs
index 068007d7f0..cc4d26158d 100644
--- a/modules/mono/glue/Managed/Files/Transform.cs
+++ b/modules/mono/glue/Managed/Files/Transform.cs
@@ -8,6 +8,7 @@ using real_t = System.Single;
namespace Godot
{
+ [Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Transform : IEquatable<Transform>
{
@@ -33,7 +34,7 @@ namespace Godot
Vector3 destinationLocation = transform.origin;
var interpolated = new Transform();
- interpolated.basis.SetQuantScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c));
+ interpolated.basis.SetQuatScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c));
interpolated.origin = sourceLocation.LinearInterpolate(destinationLocation, c);
return interpolated;
@@ -71,21 +72,21 @@ namespace Godot
{
// Make rotation matrix
// Z vector
- Vector3 zAxis = eye - target;
+ Vector3 column2 = eye - target;
- zAxis.Normalize();
+ column2.Normalize();
- Vector3 yAxis = up;
+ Vector3 column1 = up;
- Vector3 xAxis = yAxis.Cross(zAxis);
+ Vector3 column0 = column1.Cross(column2);
// Recompute Y = Z cross X
- yAxis = zAxis.Cross(xAxis);
+ column1 = column2.Cross(column0);
- xAxis.Normalize();
- yAxis.Normalize();
+ column0.Normalize();
+ column1.Normalize();
- basis = Basis.CreateFromAxes(xAxis, yAxis, zAxis);
+ basis = new Basis(column0, column1, column2);
origin = eye;
}
@@ -94,9 +95,9 @@ namespace Godot
{
return new Transform(basis, new Vector3
(
- origin[0] += basis[0].Dot(ofs),
- origin[1] += basis[1].Dot(ofs),
- origin[2] += basis[2].Dot(ofs)
+ origin[0] += basis.Row0.Dot(ofs),
+ origin[1] += basis.Row1.Dot(ofs),
+ origin[2] += basis.Row2.Dot(ofs)
));
}
@@ -104,9 +105,9 @@ namespace Godot
{
return new Vector3
(
- basis[0].Dot(v) + origin.x,
- basis[1].Dot(v) + origin.y,
- basis[2].Dot(v) + origin.z
+ basis.Row0.Dot(v) + origin.x,
+ basis.Row1.Dot(v) + origin.y,
+ basis.Row2.Dot(v) + origin.z
);
}
@@ -116,27 +117,27 @@ namespace Godot
return new Vector3
(
- basis[0, 0] * vInv.x + basis[1, 0] * vInv.y + basis[2, 0] * vInv.z,
- basis[0, 1] * vInv.x + basis[1, 1] * vInv.y + basis[2, 1] * vInv.z,
- basis[0, 2] * vInv.x + basis[1, 2] * vInv.y + basis[2, 2] * vInv.z
+ basis.Row0[0] * vInv.x + basis.Row1[0] * vInv.y + basis.Row2[0] * vInv.z,
+ basis.Row0[1] * vInv.x + basis.Row1[1] * vInv.y + basis.Row2[1] * vInv.z,
+ basis.Row0[2] * vInv.x + basis.Row1[2] * vInv.y + basis.Row2[2] * vInv.z
);
}
// Constants
private static readonly Transform _identity = new Transform(Basis.Identity, Vector3.Zero);
- private static readonly Transform _flipX = new Transform(new Basis(new Vector3(-1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1)), Vector3.Zero);
- private static readonly Transform _flipY = new Transform(new Basis(new Vector3(1, 0, 0), new Vector3(0, -1, 0), new Vector3(0, 0, 1)), Vector3.Zero);
- private static readonly Transform _flipZ = new Transform(new Basis(new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1)), Vector3.Zero);
+ private static readonly Transform _flipX = new Transform(new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1), Vector3.Zero);
+ private static readonly Transform _flipY = new Transform(new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1), Vector3.Zero);
+ private static readonly Transform _flipZ = new Transform(new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1), Vector3.Zero);
public static Transform Identity { get { return _identity; } }
public static Transform FlipX { get { return _flipX; } }
public static Transform FlipY { get { return _flipY; } }
public static Transform FlipZ { get { return _flipZ; } }
- // Constructors
- public Transform(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis, Vector3 origin)
+ // Constructors
+ public Transform(Vector3 column0, Vector3 column1, Vector3 column2, Vector3 origin)
{
- basis = Basis.CreateFromAxes(xAxis, yAxis, zAxis);
+ basis = new Basis(column0, column1, column2);
this.origin = origin;
}
diff --git a/modules/mono/glue/Managed/Files/Transform2D.cs b/modules/mono/glue/Managed/Files/Transform2D.cs
index 8d30833066..814332dc07 100644
--- a/modules/mono/glue/Managed/Files/Transform2D.cs
+++ b/modules/mono/glue/Managed/Files/Transform2D.cs
@@ -8,47 +8,71 @@ using real_t = System.Single;
namespace Godot
{
+ [Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Transform2D : IEquatable<Transform2D>
{
public Vector2 x;
public Vector2 y;
- public Vector2 o;
-
- public Vector2 Origin
- {
- get { return o; }
- }
+ public Vector2 origin;
public real_t Rotation
{
- get { return Mathf.Atan2(y.x, o.y); }
+ get
+ {
+ real_t det = BasisDeterminant();
+ Transform2D t = Orthonormalized();
+ if (det < 0)
+ {
+ t.ScaleBasis(new Vector2(1, -1));
+ }
+ return Mathf.Atan2(t.x.y, t.x.x);
+ }
+ set
+ {
+ Vector2 scale = Scale;
+ x.x = y.y = Mathf.Cos(value);
+ x.y = y.x = Mathf.Sin(value);
+ y.x *= -1;
+ Scale = scale;
+ }
}
public Vector2 Scale
{
- get { return new Vector2(x.Length(), y.Length()); }
+ get
+ {
+ real_t detSign = Mathf.Sign(BasisDeterminant());
+ return new Vector2(x.Length(), detSign * y.Length());
+ }
+ set
+ {
+ x = x.Normalized();
+ y = y.Normalized();
+ x *= value.x;
+ y *= value.y;
+ }
}
- public Vector2 this[int index]
+ public Vector2 this[int rowIndex]
{
get
{
- switch (index)
+ switch (rowIndex)
{
case 0:
return x;
case 1:
return y;
case 2:
- return o;
+ return origin;
default:
throw new IndexOutOfRangeException();
}
}
set
{
- switch (index)
+ switch (rowIndex)
{
case 0:
x = value;
@@ -57,7 +81,7 @@ namespace Godot
y = value;
return;
case 2:
- o = value;
+ origin = value;
return;
default:
throw new IndexOutOfRangeException();
@@ -65,30 +89,34 @@ namespace Godot
}
}
-
- public real_t this[int index, int axis]
+ public real_t this[int rowIndex, int columnIndex]
{
get
{
- switch (index)
+ switch (rowIndex)
{
case 0:
- return x[axis];
+ return x[columnIndex];
case 1:
- return y[axis];
+ return y[columnIndex];
+ case 2:
+ return origin[columnIndex];
default:
throw new IndexOutOfRangeException();
}
}
set
{
- switch (index)
+ switch (rowIndex)
{
case 0:
- x[axis] = value;
+ x[columnIndex] = value;
return;
case 1:
- y[axis] = value;
+ y[columnIndex] = value;
+ return;
+ case 2:
+ origin[columnIndex] = value;
return;
default:
throw new IndexOutOfRangeException();
@@ -98,34 +126,32 @@ namespace Godot
public Transform2D AffineInverse()
{
- var inv = this;
-
- real_t det = this[0, 0] * this[1, 1] - this[1, 0] * this[0, 1];
+ real_t det = BasisDeterminant();
if (det == 0)
- {
- return new Transform2D
- (
- float.NaN, float.NaN,
- float.NaN, float.NaN,
- float.NaN, float.NaN
- );
- }
+ throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");
- real_t idet = 1.0f / det;
+ var inv = this;
+
+ real_t temp = inv[0, 0];
+ inv[0, 0] = inv[1, 1];
+ inv[1, 1] = temp;
- real_t temp = this[0, 0];
- this[0, 0] = this[1, 1];
- this[1, 1] = temp;
+ real_t detInv = 1.0f / det;
- this[0] *= new Vector2(idet, -idet);
- this[1] *= new Vector2(-idet, idet);
+ inv[0] *= new Vector2(detInv, -detInv);
+ inv[1] *= new Vector2(-detInv, detInv);
- this[2] = BasisXform(-this[2]);
+ inv[2] = inv.BasisXform(-inv[2]);
return inv;
}
+ private real_t BasisDeterminant()
+ {
+ return x.x * y.y - x.y * y.x;
+ }
+
public Vector2 BasisXform(Vector2 v)
{
return new Vector2(Tdotx(v), Tdoty(v));
@@ -168,8 +194,8 @@ namespace Godot
}
// Extract parameters
- Vector2 p1 = Origin;
- Vector2 p2 = m.Origin;
+ Vector2 p1 = origin;
+ Vector2 p2 = m.origin;
// Construct matrix
var res = new Transform2D(Mathf.Atan2(v.y, v.x), p1.LinearInterpolate(p2, c));
@@ -189,7 +215,7 @@ namespace Godot
inv.x.y = inv.y.x;
inv.y.x = temp;
- inv.o = inv.BasisXform(-inv.o);
+ inv.origin = inv.BasisXform(-inv.origin);
return inv;
}
@@ -221,10 +247,18 @@ namespace Godot
var copy = this;
copy.x *= scale;
copy.y *= scale;
- copy.o *= scale;
+ copy.origin *= scale;
return copy;
}
+ private void ScaleBasis(Vector2 scale)
+ {
+ x.x *= scale.x;
+ x.y *= scale.y;
+ y.x *= scale.x;
+ y.y *= scale.y;
+ }
+
private real_t Tdotx(Vector2 with)
{
return this[0, 0] * with[0] + this[1, 0] * with[1];
@@ -238,66 +272,62 @@ namespace Godot
public Transform2D Translated(Vector2 offset)
{
var copy = this;
- copy.o += copy.BasisXform(offset);
+ copy.origin += copy.BasisXform(offset);
return copy;
}
public Vector2 Xform(Vector2 v)
{
- return new Vector2(Tdotx(v), Tdoty(v)) + o;
+ return new Vector2(Tdotx(v), Tdoty(v)) + origin;
}
public Vector2 XformInv(Vector2 v)
{
- Vector2 vInv = v - o;
+ Vector2 vInv = v - origin;
return new Vector2(x.Dot(vInv), y.Dot(vInv));
}
// Constants
- private static readonly Transform2D _identity = new Transform2D(new Vector2(1f, 0f), new Vector2(0f, 1f), Vector2.Zero);
- private static readonly Transform2D _flipX = new Transform2D(new Vector2(-1f, 0f), new Vector2(0f, 1f), Vector2.Zero);
- private static readonly Transform2D _flipY = new Transform2D(new Vector2(1f, 0f), new Vector2(0f, -1f), Vector2.Zero);
-
- public static Transform2D Identity { get { return _identity; } }
- public static Transform2D FlipX { get { return _flipX; } }
- public static Transform2D FlipY { get { return _flipY; } }
-
- // Constructors
- public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 origin)
+ private static readonly Transform2D _identity = new Transform2D(1, 0, 0, 1, 0, 0);
+ private static readonly Transform2D _flipX = new Transform2D(-1, 0, 0, 1, 0, 0);
+ private static readonly Transform2D _flipY = new Transform2D(1, 0, 0, -1, 0, 0);
+
+ public static Transform2D Identity => _identity;
+ public static Transform2D FlipX => _flipX;
+ public static Transform2D FlipY => _flipY;
+
+ // Constructors
+ public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 originPos)
{
x = xAxis;
y = yAxis;
- o = origin;
+ 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);
y = new Vector2(yx, yy);
- o = new Vector2(ox, oy);
+ origin = new Vector2(ox, oy);
}
public Transform2D(real_t rot, Vector2 pos)
{
- real_t cr = Mathf.Cos(rot);
- real_t sr = Mathf.Sin(rot);
- x.x = cr;
- y.y = cr;
- x.y = -sr;
- y.x = sr;
- o = pos;
+ x.x = y.y = Mathf.Cos(rot);
+ x.y = y.x = Mathf.Sin(rot);
+ y.x *= -1;
+ origin = pos;
}
public static Transform2D operator *(Transform2D left, Transform2D right)
{
- left.o = left.Xform(right.o);
-
- real_t x0, x1, y0, y1;
+ left.origin = left.Xform(right.origin);
- x0 = left.Tdotx(right.x);
- x1 = left.Tdoty(right.x);
- y0 = left.Tdotx(right.y);
- y1 = left.Tdoty(right.y);
+ real_t x0 = left.Tdotx(right.x);
+ real_t x1 = left.Tdoty(right.x);
+ real_t y0 = left.Tdotx(right.y);
+ real_t y1 = left.Tdoty(right.y);
left.x.x = x0;
left.x.y = x1;
@@ -319,22 +349,17 @@ namespace Godot
public override bool Equals(object obj)
{
- if (obj is Transform2D)
- {
- return Equals((Transform2D)obj);
- }
-
- return false;
+ return obj is Transform2D transform2D && Equals(transform2D);
}
public bool Equals(Transform2D other)
{
- return x.Equals(other.x) && y.Equals(other.y) && o.Equals(other.o);
+ return x.Equals(other.x) && y.Equals(other.y) && origin.Equals(other.origin);
}
public override int GetHashCode()
{
- return x.GetHashCode() ^ y.GetHashCode() ^ o.GetHashCode();
+ return x.GetHashCode() ^ y.GetHashCode() ^ origin.GetHashCode();
}
public override string ToString()
@@ -343,7 +368,7 @@ namespace Godot
{
x.ToString(),
y.ToString(),
- o.ToString()
+ origin.ToString()
});
}
@@ -353,7 +378,7 @@ namespace Godot
{
x.ToString(format),
y.ToString(format),
- o.ToString(format)
+ origin.ToString(format)
});
}
}
diff --git a/modules/mono/glue/Managed/Files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs
index 080b8802ba..0daa94057e 100644
--- a/modules/mono/glue/Managed/Files/Vector2.cs
+++ b/modules/mono/glue/Managed/Files/Vector2.cs
@@ -14,9 +14,19 @@ using real_t = System.Single;
namespace Godot
{
+ /// <summary>
+ /// 2-element structure that can be used to represent positions in 2D space or any other pair of numeric values.
+ /// </summary>
+ [Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector2 : IEquatable<Vector2>
{
+ public enum Axis
+ {
+ X = 0,
+ Y
+ }
+
public real_t x;
public real_t y;
@@ -52,11 +62,15 @@ namespace Godot
internal void Normalize()
{
- real_t length = x * x + y * y;
+ real_t lengthsq = LengthSquared();
- if (length != 0f)
+ if (lengthsq == 0)
+ {
+ x = y = 0f;
+ }
+ else
{
- length = Mathf.Sqrt(length);
+ real_t length = Mathf.Sqrt(lengthsq);
x /= length;
y /= length;
}
@@ -84,7 +98,7 @@ namespace Godot
public real_t AngleToPoint(Vector2 to)
{
- return Mathf.Atan2(x - to.x, y - to.y);
+ return Mathf.Atan2(y - to.y, x - to.x);
}
public real_t Aspect()
@@ -132,6 +146,11 @@ namespace Godot
(-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
}
+ public Vector2 DirectionTo(Vector2 b)
+ {
+ return new Vector2(b.x - x, b.y - y).Normalized();
+ }
+
public real_t DistanceSquaredTo(Vector2 to)
{
return (x - to.x) * (x - to.x) + (y - to.y) * (y - to.y);
@@ -177,11 +196,35 @@ 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 PosMod(real_t mod)
+ {
+ Vector2 v;
+ v.x = Mathf.PosMod(x, mod);
+ v.y = Mathf.PosMod(y, mod);
+ return v;
+ }
+
+ public Vector2 PosMod(Vector2 modv)
+ {
+ Vector2 v;
+ v.x = Mathf.PosMod(x, modv.x);
+ v.y = Mathf.PosMod(y, modv.y);
+ return v;
}
public Vector2 Project(Vector2 onNormal)
@@ -205,17 +248,27 @@ namespace Godot
return new Vector2(Mathf.Round(x), Mathf.Round(y));
}
+ [Obsolete("Set is deprecated. Use the Vector2(" + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
public void Set(real_t x, real_t y)
{
this.x = x;
this.y = y;
}
+ [Obsolete("Set is deprecated. Use the Vector2(" + nameof(Vector2) + ") constructor instead.", error: true)]
public void Set(Vector2 v)
{
x = v.x;
y = v.y;
}
-
+
+ public Vector2 Sign()
+ {
+ Vector2 v;
+ v.x = Mathf.Sign(x);
+ v.y = Mathf.Sign(y);
+ return v;
+ }
+
public Vector2 Slerp(Vector2 b, real_t t)
{
real_t theta = AngleTo(b);
@@ -242,10 +295,10 @@ namespace Godot
private static readonly Vector2 _one = new Vector2(1, 1);
private static readonly Vector2 _negOne = new Vector2(-1, -1);
private static readonly Vector2 _inf = new Vector2(Mathf.Inf, Mathf.Inf);
-
+
private static readonly Vector2 _up = new Vector2(0, -1);
private static readonly Vector2 _down = new Vector2(0, 1);
- private static readonly Vector2 _right = new Vector2(1, 0);
+ private static readonly Vector2 _right = new Vector2(1, 0);
private static readonly Vector2 _left = new Vector2(-1, 0);
public static Vector2 Zero { get { return _zero; } }
@@ -326,6 +379,20 @@ namespace Godot
return left;
}
+ public static Vector2 operator %(Vector2 vec, real_t divisor)
+ {
+ vec.x %= divisor;
+ vec.y %= divisor;
+ return vec;
+ }
+
+ public static Vector2 operator %(Vector2 vec, Vector2 divisorv)
+ {
+ vec.x %= divisorv.x;
+ vec.y %= divisorv.y;
+ return vec;
+ }
+
public static bool operator ==(Vector2 left, Vector2 right)
{
return left.Equals(right);
@@ -338,7 +405,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;
}
@@ -348,7 +415,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;
}
@@ -358,7 +425,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;
}
@@ -368,7 +435,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;
}
@@ -388,7 +455,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 6fffe5e4d6..9076dbd3b0 100644
--- a/modules/mono/glue/Managed/Files/Vector3.cs
+++ b/modules/mono/glue/Managed/Files/Vector3.cs
@@ -14,6 +14,10 @@ using real_t = System.Single;
namespace Godot
{
+ /// <summary>
+ /// 3-element structure that can be used to represent positions in 3D space or any other pair of numeric values.
+ /// </summary>
+ [Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector3 : IEquatable<Vector3>
{
@@ -65,14 +69,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;
@@ -126,6 +131,11 @@ namespace Godot
);
}
+ public Vector3 DirectionTo(Vector3 b)
+ {
+ return new Vector3(b.x - x, b.y - y, b.z - z).Normalized();
+ }
+
public real_t DistanceSquaredTo(Vector3 b)
{
return (b - this).LengthSquared();
@@ -184,6 +194,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);
@@ -204,12 +222,30 @@ namespace Godot
public Basis Outer(Vector3 b)
{
return new Basis(
- new Vector3(x * b.x, x * b.y, x * b.z),
- new Vector3(y * b.x, y * b.y, y * b.z),
- new Vector3(z * b.x, z * b.y, z * b.z)
+ x * b.x, x * b.y, x * b.z,
+ y * b.x, y * b.y, y * b.z,
+ z * b.x, z * b.y, z * b.z
);
}
+ public Vector3 PosMod(real_t mod)
+ {
+ Vector3 v;
+ v.x = Mathf.PosMod(x, mod);
+ v.y = Mathf.PosMod(y, mod);
+ v.z = Mathf.PosMod(z, mod);
+ return v;
+ }
+
+ public Vector3 PosMod(Vector3 modv)
+ {
+ Vector3 v;
+ v.x = Mathf.PosMod(x, modv.x);
+ v.y = Mathf.PosMod(y, modv.y);
+ v.z = Mathf.PosMod(z, modv.z);
+ return v;
+ }
+
public Vector3 Project(Vector3 onNormal)
{
return onNormal * (Dot(onNormal) / onNormal.LengthSquared());
@@ -234,12 +270,14 @@ namespace Godot
return new Basis(axis, phi).Xform(this);
}
+ [Obsolete("Set is deprecated. Use the Vector3(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
public void Set(real_t x, real_t y, real_t z)
{
this.x = x;
this.y = y;
this.z = z;
}
+ [Obsolete("Set is deprecated. Use the Vector3(" + nameof(Vector3) + ") constructor instead.", error: true)]
public void Set(Vector3 v)
{
x = v.x;
@@ -247,6 +285,15 @@ namespace Godot
z = v.z;
}
+ public Vector3 Sign()
+ {
+ Vector3 v;
+ v.x = Mathf.Sign(x);
+ v.y = Mathf.Sign(y);
+ v.z = Mathf.Sign(z);
+ return v;
+ }
+
public Vector3 Slerp(Vector3 b, real_t t)
{
real_t theta = AngleTo(b);
@@ -276,13 +323,13 @@ namespace Godot
0f, 0f, z
);
}
-
+
// Constants
private static readonly Vector3 _zero = new Vector3(0, 0, 0);
private static readonly Vector3 _one = new Vector3(1, 1, 1);
private static readonly Vector3 _negOne = new Vector3(-1, -1, -1);
private static readonly Vector3 _inf = new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf);
-
+
private static readonly Vector3 _up = new Vector3(0, 1, 0);
private static readonly Vector3 _down = new Vector3(0, -1, 0);
private static readonly Vector3 _right = new Vector3(1, 0, 0);
@@ -294,7 +341,7 @@ namespace Godot
public static Vector3 One { get { return _one; } }
public static Vector3 NegOne { get { return _negOne; } }
public static Vector3 Inf { get { return _inf; } }
-
+
public static Vector3 Up { get { return _up; } }
public static Vector3 Down { get { return _down; } }
public static Vector3 Right { get { return _right; } }
@@ -380,6 +427,22 @@ namespace Godot
return left;
}
+ public static Vector3 operator %(Vector3 vec, real_t divisor)
+ {
+ vec.x %= divisor;
+ vec.y %= divisor;
+ vec.z %= divisor;
+ return vec;
+ }
+
+ public static Vector3 operator %(Vector3 vec, Vector3 divisorv)
+ {
+ vec.x %= divisorv.x;
+ vec.y %= divisorv.y;
+ vec.z %= divisorv.z;
+ return vec;
+ }
+
public static bool operator ==(Vector3 left, Vector3 right)
{
return left.Equals(right);
@@ -392,9 +455,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;
}
@@ -404,9 +467,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;
}
@@ -416,9 +479,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;
}
@@ -428,9 +491,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;
}
@@ -450,7 +513,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/Managed/IgnoredFiles/Variant.cs b/modules/mono/glue/Managed/IgnoredFiles/Variant.cs
new file mode 100644
index 0000000000..802140b062
--- /dev/null
+++ b/modules/mono/glue/Managed/IgnoredFiles/Variant.cs
@@ -0,0 +1,11 @@
+
+namespace Godot
+{
+ public static class Variant
+ {
+ public enum Type
+ {
+
+ }
+ }
+}
diff --git a/modules/mono/glue/Managed/Managed.csproj b/modules/mono/glue/Managed/Managed.csproj
index 1f82dde5e7..c8eca71199 100644
--- a/modules/mono/glue/Managed/Managed.csproj
+++ b/modules/mono/glue/Managed/Managed.csproj
@@ -1,4 +1,4 @@
-<?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>
@@ -8,10 +8,11 @@
<RootNamespace>Managed</RootNamespace>
<AssemblyName>Managed</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <LangVersion>7</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
+ <DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
@@ -37,4 +38,4 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-</Project>
+</Project> \ No newline at end of file
diff --git a/modules/mono/glue/Managed/Properties/AssemblyInfo.cs b/modules/mono/glue/Managed/Properties/AssemblyInfo.cs
index 7ed68acad7..77b3774e81 100644
--- a/modules/mono/glue/Managed/Properties/AssemblyInfo.cs
+++ b/modules/mono/glue/Managed/Properties/AssemblyInfo.cs
@@ -1,7 +1,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
-// Information about this assembly is defined by the following attributes.
+// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("Managed")]
@@ -19,7 +19,7 @@ using System.Runtime.CompilerServices;
[assembly: AssemblyVersion("1.0.*")]
-// The following attributes are used to specify the signing key for the assembly,
+// 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)]
diff --git a/modules/mono/glue/arguments_vector.h b/modules/mono/glue/arguments_vector.h
new file mode 100644
index 0000000000..8c0f308c15
--- /dev/null
+++ b/modules/mono/glue/arguments_vector.h
@@ -0,0 +1,68 @@
+/*************************************************************************/
+/* arguments_vector.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 ARGUMENTS_VECTOR_H
+#define ARGUMENTS_VECTOR_H
+
+#include "core/os/memory.h"
+
+template <typename T, int POOL_SIZE = 5>
+struct ArgumentsVector {
+
+private:
+ T pool[POOL_SIZE];
+ T *_ptr;
+ int size;
+
+ ArgumentsVector();
+ ArgumentsVector(const ArgumentsVector &);
+
+public:
+ T *ptr() { return _ptr; }
+ T &get(int p_idx) { return _ptr[p_idx]; }
+ void set(int p_idx, const T &p_value) { _ptr[p_idx] = p_value; }
+
+ explicit ArgumentsVector(int p_size) :
+ size(p_size) {
+ if (p_size <= POOL_SIZE) {
+ _ptr = pool;
+ } else {
+ _ptr = memnew_arr(T, p_size);
+ }
+ }
+
+ ~ArgumentsVector() {
+ if (size > POOL_SIZE) {
+ memdelete_arr(_ptr);
+ }
+ }
+};
+
+#endif // ARGUMENTS_VECTOR_H
diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp
index d718c3cc61..6d85f55b97 100644
--- a/modules/mono/glue/base_object_glue.cpp
+++ b/modules/mono/glue/base_object_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -33,12 +33,14 @@
#ifdef MONO_GLUE_ENABLED
#include "core/reference.h"
-#include "core/string_db.h"
+#include "core/string_name.h"
#include "../csharp_script.h"
+#include "../mono_gd/gd_mono_class.h"
#include "../mono_gd/gd_mono_internals.h"
#include "../mono_gd/gd_mono_utils.h"
#include "../signal_awaiter_utils.h"
+#include "arguments_vector.h"
Object *godot_icall_Object_Ctor(MonoObject *p_obj) {
Object *instance = memnew(Object);
@@ -54,8 +56,10 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
if (p_ptr->get_script_instance()) {
CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance());
if (cs_instance) {
- cs_instance->mono_object_disposed(p_obj);
- p_ptr->set_script_instance(NULL);
+ if (!cs_instance->is_destructing_script_instance()) {
+ cs_instance->mono_object_disposed(p_obj);
+ p_ptr->set_script_instance(NULL);
+ }
return;
}
}
@@ -63,14 +67,17 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
void *data = p_ptr->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
if (data) {
- Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
- if (gchandle.is_valid()) {
- CSharpLanguage::release_script_gchandle(p_obj, gchandle);
+ CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
+ if (script_binding.inited) {
+ Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ if (gchandle.is_valid()) {
+ CSharpLanguage::release_script_gchandle(p_obj, gchandle);
+ }
}
}
}
-void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer) {
+void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer) {
#ifdef DEBUG_ENABLED
CRASH_COND(p_ptr == NULL);
// This is only called with Reference derived classes
@@ -82,12 +89,17 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_
if (ref->get_script_instance()) {
CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(ref->get_script_instance());
if (cs_instance) {
- bool r_owner_deleted;
- cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, r_owner_deleted);
- if (!r_owner_deleted && !p_is_finalizer) {
- // If the native instance is still alive and Dispose() was called
- // (instead of the finalizer), then we remove the script instance.
- ref->set_script_instance(NULL);
+ if (!cs_instance->is_destructing_script_instance()) {
+ bool delete_owner;
+ bool remove_script_instance;
+
+ cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, delete_owner, remove_script_instance);
+
+ if (delete_owner) {
+ memdelete(ref);
+ } else if (remove_script_instance) {
+ ref->set_script_instance(NULL);
+ }
}
return;
}
@@ -101,9 +113,12 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_
void *data = ref->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
if (data) {
- Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
- if (gchandle.is_valid()) {
- CSharpLanguage::release_script_gchandle(p_obj, gchandle);
+ CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
+ if (script_binding.inited) {
+ Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ if (gchandle.is_valid()) {
+ CSharpLanguage::release_script_gchandle(p_obj, gchandle);
+ }
}
}
}
@@ -134,7 +149,7 @@ MonoObject *godot_icall_Object_weakref(Object *p_obj) {
wref->set_obj(p_obj);
}
- return GDMonoUtils::create_managed_for_godot_object(CACHED_CLASS(WeakRef), Reference::get_class_static(), Object::cast_to<Object>(wref.ptr()));
+ return GDMonoUtils::unmanaged_get_managed(wref.ptr());
}
Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter) {
@@ -142,13 +157,94 @@ Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal,
return SignalAwaiterUtils::connect_signal_awaiter(p_source, signal, p_target, p_awaiter);
}
+MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) {
+ List<PropertyInfo> property_list;
+ p_ptr->get_property_list(&property_list);
+
+ MonoArray *result = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), property_list.size());
+
+ 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_setref(result, i, boxed);
+ i++;
+ }
+
+ return result;
+}
+
+MonoBoolean godot_icall_DynamicGodotObject_InvokeMember(Object *p_ptr, MonoString *p_name, MonoArray *p_args, MonoObject **r_result) {
+ String name = GDMonoMarshal::mono_string_to_godot(p_name);
+
+ int argc = mono_array_length(p_args);
+
+ ArgumentsVector<Variant> arg_store(argc);
+ ArgumentsVector<const Variant *> args(argc);
+
+ for (int i = 0; i < argc; i++) {
+ MonoObject *elem = mono_array_get(p_args, MonoObject *, i);
+ arg_store.set(i, GDMonoMarshal::mono_object_to_variant(elem));
+ args.set(i, &arg_store.get(i));
+ }
+
+ Variant::CallError error;
+ Variant result = p_ptr->call(StringName(name), args.ptr(), argc, error);
+
+ *r_result = GDMonoMarshal::variant_to_mono_object(result);
+
+ return error.error == Variant::CallError::CALL_OK;
+}
+
+MonoBoolean godot_icall_DynamicGodotObject_GetMember(Object *p_ptr, MonoString *p_name, MonoObject **r_result) {
+ String name = GDMonoMarshal::mono_string_to_godot(p_name);
+
+ bool valid;
+ Variant value = p_ptr->get(StringName(name), &valid);
+
+ if (valid) {
+ *r_result = GDMonoMarshal::variant_to_mono_object(value);
+ }
+
+ return valid;
+}
+
+MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *p_name, MonoObject *p_value) {
+ String name = GDMonoMarshal::mono_string_to_godot(p_name);
+ Variant value = GDMonoMarshal::mono_object_to_variant(p_value);
+
+ bool valid;
+ p_ptr->set(StringName(name), value, &valid);
+
+ return valid;
+}
+
+MonoString *godot_icall_Object_ToString(Object *p_ptr) {
+#ifdef DEBUG_ENABLED
+ // Cannot happen in C#; would get an ObjectDisposedException instead.
+ CRASH_COND(p_ptr == NULL);
+
+ if (ScriptDebugger::get_singleton() && !Object::cast_to<Reference>(p_ptr)) { // Only if debugging!
+ // Cannot happen either in C#; the handle is nullified when the object is destroyed
+ CRASH_COND(!ObjectDB::instance_validate(p_ptr));
+ }
+#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() {
mono_add_internal_call("Godot.Object::godot_icall_Object_Ctor", (void *)godot_icall_Object_Ctor);
mono_add_internal_call("Godot.Object::godot_icall_Object_Disposed", (void *)godot_icall_Object_Disposed);
mono_add_internal_call("Godot.Object::godot_icall_Reference_Disposed", (void *)godot_icall_Reference_Disposed);
mono_add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", (void *)godot_icall_Object_ClassDB_get_method);
+ mono_add_internal_call("Godot.Object::godot_icall_Object_ToString", (void *)godot_icall_Object_ToString);
mono_add_internal_call("Godot.Object::godot_icall_Object_weakref", (void *)godot_icall_Object_weakref);
mono_add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", (void *)godot_icall_SignalAwaiter_connect);
+ mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMemberList", (void *)godot_icall_DynamicGodotObject_SetMemberList);
+ mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_InvokeMember", (void *)godot_icall_DynamicGodotObject_InvokeMember);
+ mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_GetMember", (void *)godot_icall_DynamicGodotObject_GetMember);
+ mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMember", (void *)godot_icall_DynamicGodotObject_SetMember);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/base_object_glue.h b/modules/mono/glue/base_object_glue.h
index 2d4d66ebb8..e4ac9fb99d 100644
--- a/modules/mono/glue/base_object_glue.h
+++ b/modules/mono/glue/base_object_glue.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -42,7 +42,7 @@ Object *godot_icall_Object_Ctor(MonoObject *p_obj);
void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr);
-void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer);
+void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer);
MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method);
@@ -50,6 +50,18 @@ MonoObject *godot_icall_Object_weakref(Object *p_obj);
Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter);
+// DynamicGodotObject
+
+MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr);
+
+MonoBoolean godot_icall_DynamicGodotObject_InvokeMember(Object *p_ptr, MonoString *p_name, MonoArray *p_args, MonoObject **r_result);
+
+MonoBoolean godot_icall_DynamicGodotObject_GetMember(Object *p_ptr, MonoString *p_name, MonoObject **r_result);
+
+MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *p_name, MonoObject *p_value);
+
+MonoString *godot_icall_Object_ToString(Object *p_ptr);
+
// Register internal calls
void godot_register_object_icalls();
diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp
index d9dba1c60d..e67c8b9ad9 100644
--- a/modules/mono/glue/collections_glue.cpp
+++ b/modules/mono/glue/collections_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -46,15 +46,23 @@ 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;
}
return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index));
}
+MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class) {
+ if (index < 0 || index >= ptr->size()) {
+ GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
+ return NULL;
+ }
+ return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index), ManagedType(type_encoding, type_class));
+}
+
void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) {
- if (index < 0 || index > ptr->size()) {
+ if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return;
}
@@ -65,20 +73,21 @@ int godot_icall_Array_Count(Array *ptr) {
return ptr->size();
}
-void godot_icall_Array_Add(Array *ptr, MonoObject *item) {
+int godot_icall_Array_Add(Array *ptr, MonoObject *item) {
ptr->append(GDMonoMarshal::mono_object_to_variant(item));
+ return ptr->size();
}
void godot_icall_Array_Clear(Array *ptr) {
ptr->clear();
}
-bool godot_icall_Array_Contains(Array *ptr, MonoObject *item) {
+MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item) {
return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1;
}
void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) {
- int count = ptr->size();
+ unsigned int count = ptr->size();
if (mono_array_length(array) < (array_index + count)) {
MonoException *exc = mono_get_exception_argument("", "Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
@@ -86,7 +95,7 @@ void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) {
return;
}
- for (int i = 0; i < count; i++) {
+ for (unsigned int i = 0; i < count; i++) {
MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(ptr->operator[](i));
mono_array_setref(array, array_index, boxed);
array_index++;
@@ -105,7 +114,7 @@ void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item) {
ptr->insert(index, GDMonoMarshal::mono_object_to_variant(item));
}
-bool godot_icall_Array_Remove(Array *ptr, MonoObject *item) {
+MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) {
int idx = ptr->find(GDMonoMarshal::mono_object_to_variant(item));
if (idx >= 0) {
ptr->remove(idx);
@@ -115,13 +124,29 @@ bool 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;
}
ptr->remove(index);
}
+Error godot_icall_Array_Resize(Array *ptr, int new_size) {
+ return ptr->resize(new_size);
+}
+
+void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) {
+ MonoType *elem_type = mono_reflection_type_get_type(refltype);
+
+ *type_encoding = mono_type_get_type(elem_type);
+ MonoClass *type_class_raw = mono_class_from_mono_type(elem_type);
+ *type_class = GDMono::get_singleton()->get_class(type_class_raw);
+}
+
+MonoString *godot_icall_Array_ToString(Array *ptr) {
+ return GDMonoMarshal::mono_string_from_godot(Variant(*ptr).operator String());
+}
+
Dictionary *godot_icall_Dictionary_Ctor() {
return memnew(Dictionary);
}
@@ -137,13 +162,27 @@ 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;
}
return GDMonoMarshal::variant_to_mono_object(ret);
}
+MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class) {
+ Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
+ if (ret == NULL) {
+ MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr());
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!exc);
+#endif
+ GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException));
+ GDMonoUtils::set_pending_exception((MonoException *)exc);
+ return NULL;
+ }
+ return GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class));
+}
+
void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value) {
ptr->operator[](GDMonoMarshal::mono_object_to_variant(key)) = GDMonoMarshal::mono_object_to_variant(value);
}
@@ -174,21 +213,21 @@ void godot_icall_Dictionary_Clear(Dictionary *ptr) {
ptr->clear();
}
-bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) {
+MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) {
// no dupes
Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
return ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value);
}
-bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) {
+MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) {
return ptr->has(GDMonoMarshal::mono_object_to_variant(key));
}
-bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) {
+MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) {
return ptr->erase(GDMonoMarshal::mono_object_to_variant(key));
}
-bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value) {
+MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value) {
Variant varKey = GDMonoMarshal::mono_object_to_variant(key);
// no dupes
@@ -201,7 +240,7 @@ bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject
return false;
}
-bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) {
+MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) {
Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
if (ret == NULL) {
*value = NULL;
@@ -211,10 +250,33 @@ bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoOb
return true;
}
+MonoBoolean godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class) {
+ Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
+ if (ret == NULL) {
+ *value = NULL;
+ return false;
+ }
+ *value = GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class));
+ return true;
+}
+
+void godot_icall_Dictionary_Generic_GetValueTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) {
+ MonoType *value_type = mono_reflection_type_get_type(refltype);
+
+ *type_encoding = mono_type_get_type(value_type);
+ MonoClass *type_class_raw = mono_class_from_mono_type(value_type);
+ *type_class = GDMono::get_singleton()->get_class(type_class_raw);
+}
+
+MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr) {
+ return GDMonoMarshal::mono_string_from_godot(Variant(*ptr).operator String());
+}
+
void godot_register_collections_icalls() {
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At", (void *)godot_icall_Array_At);
+ mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", (void *)godot_icall_Array_At_Generic);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", (void *)godot_icall_Array_SetAt);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add);
@@ -225,10 +287,14 @@ void godot_register_collections_icalls() {
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", (void *)godot_icall_Array_RemoveAt);
+ mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Resize", (void *)godot_icall_Array_Resize);
+ mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", (void *)godot_icall_Array_Generic_GetElementTypeInfo);
+ mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_ToString", (void *)godot_icall_Array_ToString);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", (void *)godot_icall_Dictionary_Ctor);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", (void *)godot_icall_Dictionary_Dtor);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", (void *)godot_icall_Dictionary_GetValue);
+ mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", (void *)godot_icall_Dictionary_GetValue_Generic);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", (void *)godot_icall_Dictionary_SetValue);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", (void *)godot_icall_Dictionary_Keys);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", (void *)godot_icall_Dictionary_Values);
@@ -240,6 +306,9 @@ void godot_register_collections_icalls() {
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue);
+ mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", (void *)godot_icall_Dictionary_TryGetValue_Generic);
+ mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", (void *)godot_icall_Dictionary_Generic_GetValueTypeInfo);
+ mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ToString", (void *)godot_icall_Dictionary_ToString);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/collections_glue.h b/modules/mono/glue/collections_glue.h
index fa8e4c28aa..54ed42c3b6 100644
--- a/modules/mono/glue/collections_glue.h
+++ b/modules/mono/glue/collections_glue.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -45,15 +45,17 @@ void godot_icall_Array_Dtor(Array *ptr);
MonoObject *godot_icall_Array_At(Array *ptr, int index);
+MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class);
+
void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value);
int godot_icall_Array_Count(Array *ptr);
-void godot_icall_Array_Add(Array *ptr, MonoObject *item);
+int godot_icall_Array_Add(Array *ptr, MonoObject *item);
void godot_icall_Array_Clear(Array *ptr);
-bool godot_icall_Array_Contains(Array *ptr, MonoObject *item);
+MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item);
void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index);
@@ -61,10 +63,16 @@ int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item);
void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item);
-bool godot_icall_Array_Remove(Array *ptr, MonoObject *item);
+MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item);
void godot_icall_Array_RemoveAt(Array *ptr, int index);
+Error godot_icall_Array_Resize(Array *ptr, int new_size);
+
+void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class);
+
+MonoString *godot_icall_Array_ToString(Array *ptr);
+
// Dictionary
Dictionary *godot_icall_Dictionary_Ctor();
@@ -73,6 +81,8 @@ void godot_icall_Dictionary_Dtor(Dictionary *ptr);
MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key);
+MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class);
+
void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value);
Array *godot_icall_Dictionary_Keys(Dictionary *ptr);
@@ -85,15 +95,21 @@ void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *va
void godot_icall_Dictionary_Clear(Dictionary *ptr);
-bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value);
+MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value);
+
+MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key);
+
+MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key);
+
+MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value);
-bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key);
+MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value);
-bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key);
+MonoBoolean godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class);
-bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value);
+void godot_icall_Dictionary_Generic_GetValueTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class);
-bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value);
+MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr);
// Register internal calls
diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp
index 051f42b966..8b9a1380d8 100644
--- a/modules/mono/glue/gd_glue.cpp
+++ b/modules/mono/glue/gd_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -41,18 +41,18 @@
#include "../mono_gd/gd_mono_utils.h"
-MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes) {
+MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects) {
Variant ret;
PoolByteArray varr = GDMonoMarshal::mono_array_to_PoolByteArray(p_bytes);
PoolByteArray::Read r = varr.read();
- Error err = decode_variant(ret, r.ptr(), varr.size(), NULL);
+ Error err = decode_variant(ret, r.ptr(), varr.size(), NULL, p_allow_objects);
if (err != OK) {
ret = RTR("Not enough bytes for decoding bytes, or invalid format.");
}
return GDMonoMarshal::variant_to_mono_object(ret);
}
-MonoObject *godot_icall_GD_convert(MonoObject *p_what, int p_type) {
+MonoObject *godot_icall_GD_convert(MonoObject *p_what, int32_t p_type) {
Variant what = GDMonoMarshal::mono_object_to_variant(p_what);
const Variant *args[1] = { &what };
Variant::CallError ce;
@@ -65,7 +65,7 @@ int godot_icall_GD_hash(MonoObject *p_var) {
return GDMonoMarshal::mono_object_to_variant(p_var).hash();
}
-MonoObject *godot_icall_GD_instance_from_id(int p_instance_id) {
+MonoObject *godot_icall_GD_instance_from_id(uint64_t p_instance_id) {
return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(p_instance_id));
}
@@ -115,7 +115,29 @@ void godot_icall_GD_printt(MonoArray *p_what) {
print_line(str);
}
-void godot_icall_GD_seed(int p_seed) {
+float godot_icall_GD_randf() {
+ return Math::randf();
+}
+
+uint32_t godot_icall_GD_randi() {
+ return Math::rand();
+}
+
+void godot_icall_GD_randomize() {
+ Math::randomize();
+}
+
+double godot_icall_GD_rand_range(double from, double to) {
+ return Math::random(from, to);
+}
+
+uint32_t godot_icall_GD_rand_seed(uint64_t seed, uint64_t *newSeed) {
+ int ret = Math::rand_from_seed(&seed);
+ *newSeed = seed;
+ return ret;
+}
+
+void godot_icall_GD_seed(uint64_t p_seed) {
Math::seed(p_seed);
}
@@ -145,7 +167,7 @@ MonoObject *godot_icall_GD_str2var(MonoString *p_str) {
int line;
Error err = VariantParser::parse(&ss, ret, errs, line);
if (err != OK) {
- String err_str = "Parse error at line " + itos(line) + ": " + errs;
+ String err_str = "Parse error at line " + itos(line) + ": " + errs + ".";
ERR_PRINTS(err_str);
ret = err_str;
}
@@ -153,23 +175,30 @@ MonoObject *godot_icall_GD_str2var(MonoString *p_str) {
return GDMonoMarshal::variant_to_mono_object(ret);
}
-bool godot_icall_GD_type_exists(MonoString *p_type) {
+MonoBoolean godot_icall_GD_type_exists(MonoString *p_type) {
return ClassDB::class_exists(GDMonoMarshal::mono_string_to_godot(p_type));
}
-MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var) {
+void godot_icall_GD_pusherror(MonoString *p_str) {
+ ERR_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str));
+}
+
+void godot_icall_GD_pushwarning(MonoString *p_str) {
+ WARN_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str));
+}
+
+MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_objects) {
Variant var = GDMonoMarshal::mono_object_to_variant(p_var);
PoolByteArray barr;
int len;
- Error err = encode_variant(var, NULL, len);
- ERR_EXPLAIN("Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID).");
- ERR_FAIL_COND_V(err != OK, NULL);
+ Error err = encode_variant(var, NULL, len, p_full_objects);
+ ERR_FAIL_COND_V_MSG(err != OK, NULL, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID).");
barr.resize(len);
{
PoolByteArray::Write w = barr.write();
- encode_variant(var, w.ptr(), len);
+ encode_variant(var, w.ptr(), len, p_full_objects);
}
return GDMonoMarshal::PoolByteArray_to_mono_array(barr);
@@ -181,22 +210,36 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) {
return GDMonoMarshal::mono_string_from_godot(vars);
}
+MonoObject *godot_icall_DefaultGodotTaskScheduler() {
+ return GDMonoUtils::mono_cache.task_scheduler_handle->get_target();
+}
+
void godot_register_gd_icalls() {
mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var);
mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert);
mono_add_internal_call("Godot.GD::godot_icall_GD_hash", (void *)godot_icall_GD_hash);
mono_add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", (void *)godot_icall_GD_instance_from_id);
+ mono_add_internal_call("Godot.GD::godot_icall_GD_pusherror", (void *)godot_icall_GD_pusherror);
+ mono_add_internal_call("Godot.GD::godot_icall_GD_pushwarning", (void *)godot_icall_GD_pushwarning);
mono_add_internal_call("Godot.GD::godot_icall_GD_print", (void *)godot_icall_GD_print);
mono_add_internal_call("Godot.GD::godot_icall_GD_printerr", (void *)godot_icall_GD_printerr);
mono_add_internal_call("Godot.GD::godot_icall_GD_printraw", (void *)godot_icall_GD_printraw);
mono_add_internal_call("Godot.GD::godot_icall_GD_prints", (void *)godot_icall_GD_prints);
mono_add_internal_call("Godot.GD::godot_icall_GD_printt", (void *)godot_icall_GD_printt);
+ mono_add_internal_call("Godot.GD::godot_icall_GD_randf", (void *)godot_icall_GD_randf);
+ mono_add_internal_call("Godot.GD::godot_icall_GD_randi", (void *)godot_icall_GD_randi);
+ mono_add_internal_call("Godot.GD::godot_icall_GD_randomize", (void *)godot_icall_GD_randomize);
+ mono_add_internal_call("Godot.GD::godot_icall_GD_rand_range", (void *)godot_icall_GD_rand_range);
+ mono_add_internal_call("Godot.GD::godot_icall_GD_rand_seed", (void *)godot_icall_GD_rand_seed);
mono_add_internal_call("Godot.GD::godot_icall_GD_seed", (void *)godot_icall_GD_seed);
mono_add_internal_call("Godot.GD::godot_icall_GD_str", (void *)godot_icall_GD_str);
mono_add_internal_call("Godot.GD::godot_icall_GD_str2var", (void *)godot_icall_GD_str2var);
mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists);
mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes);
mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str);
+
+ // Dispatcher
+ mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h
index 6f846f221d..a34c0bc50f 100644
--- a/modules/mono/glue/gd_glue.h
+++ b/modules/mono/glue/gd_glue.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -35,13 +35,13 @@
#include "../mono_gd/gd_mono_marshal.h"
-MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes);
+MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects);
-MonoObject *godot_icall_GD_convert(MonoObject *p_what, int p_type);
+MonoObject *godot_icall_GD_convert(MonoObject *p_what, int32_t p_type);
int godot_icall_GD_hash(MonoObject *p_var);
-MonoObject *godot_icall_GD_instance_from_id(int p_instance_id);
+MonoObject *godot_icall_GD_instance_from_id(uint64_t p_instance_id);
void godot_icall_GD_print(MonoArray *p_what);
@@ -53,18 +53,30 @@ void godot_icall_GD_prints(MonoArray *p_what);
void godot_icall_GD_printt(MonoArray *p_what);
-void godot_icall_GD_seed(int p_seed);
+float godot_icall_GD_randf();
+
+uint32_t godot_icall_GD_randi();
+
+void godot_icall_GD_randomize();
+
+double godot_icall_GD_rand_range(double from, double to);
+
+uint32_t godot_icall_GD_rand_seed(uint64_t seed, uint64_t *newSeed);
+
+void godot_icall_GD_seed(uint64_t p_seed);
MonoString *godot_icall_GD_str(MonoArray *p_what);
MonoObject *godot_icall_GD_str2var(MonoString *p_str);
-bool godot_icall_GD_type_exists(MonoString *p_type);
+MonoBoolean godot_icall_GD_type_exists(MonoString *p_type);
-MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var);
+MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_objects);
MonoString *godot_icall_GD_var2str(MonoObject *p_var);
+MonoObject *godot_icall_DefaultGodotTaskScheduler();
+
// Register internal calls
void godot_register_gd_icalls();
diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h
index 69c5c6dcdb..1836130b76 100644
--- a/modules/mono/glue/glue_header.h
+++ b/modules/mono/glue/glue_header.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -74,4 +74,6 @@ void godot_register_glue_header_icalls() {
} \
Object *m_instance = ci->creation_func();
+#include "arguments_vector.h"
+
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/nodepath_glue.cpp b/modules/mono/glue/nodepath_glue.cpp
index 4b7648a4f9..2edc43af8d 100644
--- a/modules/mono/glue/nodepath_glue.cpp
+++ b/modules/mono/glue/nodepath_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -40,7 +40,7 @@ NodePath *godot_icall_NodePath_Ctor(MonoString *p_path) {
void godot_icall_NodePath_Dtor(NodePath *p_ptr) {
ERR_FAIL_NULL(p_ptr);
- _GodotSharp::get_singleton()->queue_dispose(p_ptr);
+ memdelete(p_ptr);
}
MonoString *godot_icall_NodePath_operator_String(NodePath *p_np) {
diff --git a/modules/mono/glue/nodepath_glue.h b/modules/mono/glue/nodepath_glue.h
index 92579399a6..2192444336 100644
--- a/modules/mono/glue/nodepath_glue.h
+++ b/modules/mono/glue/nodepath_glue.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp
index 5d66b8aa6f..6a9810d0d4 100644
--- a/modules/mono/glue/rid_glue.cpp
+++ b/modules/mono/glue/rid_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -45,7 +45,7 @@ RID *godot_icall_RID_Ctor(Object *p_from) {
void godot_icall_RID_Dtor(RID *p_ptr) {
ERR_FAIL_NULL(p_ptr);
- _GodotSharp::get_singleton()->queue_dispose(p_ptr);
+ memdelete(p_ptr);
}
uint32_t godot_icall_RID_get_id(RID *p_ptr) {
diff --git a/modules/mono/glue/rid_glue.h b/modules/mono/glue/rid_glue.h
index c725a9b5de..4b593e8a95 100644
--- a/modules/mono/glue/rid_glue.h
+++ b/modules/mono/glue/rid_glue.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp
index e1a2f1affd..e9373fb486 100644
--- a/modules/mono/glue/string_glue.cpp
+++ b/modules/mono/glue/string_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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/glue/string_glue.h b/modules/mono/glue/string_glue.h
index 8b1553e28b..c9052edd40 100644
--- a/modules/mono/glue/string_glue.h
+++ b/modules/mono/glue/string_glue.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h
index 39d608de9f..4c17a6ec9d 100644
--- a/modules/mono/godotsharp_defs.h
+++ b/modules/mono/godotsharp_defs.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -36,9 +36,11 @@
#define BINDINGS_GLOBAL_SCOPE_CLASS "GD"
#define BINDINGS_PTR_FIELD "ptr"
#define BINDINGS_NATIVE_NAME_FIELD "nativeName"
-#define API_ASSEMBLY_NAME "GodotSharp"
+#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_ASM_NAME "GodotTools"
+#define TOOLS_PROJECT_EDITOR_ASM_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 2570e68f13..4b2525c692 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -30,15 +30,19 @@
#include "godotsharp_dirs.h"
+#include "core/os/dir_access.h"
#include "core/os/os.h"
+#include "core/project_settings.h"
#ifdef TOOLS_ENABLED
-#include "core/os/dir_access.h"
-#include "core/project_settings.h"
#include "core/version.h"
#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;
@@ -95,15 +114,27 @@ public:
#ifdef TOOLS_ENABLED
String mono_solutions_dir;
String build_logs_dir;
+
String sln_filepath;
String csproj_filepath;
+
+ String data_editor_tools_dir;
+ String data_editor_prebuilt_api_dir;
+#endif
+
+ String data_mono_etc_dir;
+ String data_mono_lib_dir;
+
+#ifdef WINDOWS_ENABLED
+ String data_mono_bin_dir;
#endif
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
@@ -118,15 +149,83 @@ 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 = String("res://") + name;
+ String base_path = ProjectSettings::get_singleton()->globalize_path("res://");
+
+ 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();
+
+#ifdef TOOLS_ENABLED
+
+ String data_dir_root = exe_dir.plus_file("GodotSharp");
+ data_editor_tools_dir = data_dir_root.plus_file("Tools");
+ data_editor_prebuilt_api_dir = data_dir_root.plus_file("Api");
+
+ 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");
+#endif
+
+#ifdef OSX_ENABLED
+ if (!DirAccess::exists(data_editor_tools_dir)) {
+ data_editor_tools_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Tools");
+ }
+
+ if (!DirAccess::exists(data_editor_prebuilt_api_dir)) {
+ data_editor_prebuilt_api_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Api");
+ }
+
+ if (!DirAccess::exists(data_mono_root_dir)) {
+ data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc");
+ data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib");
+ }
+#endif
+
+#else
+
+ 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");
+#endif
+
+#ifdef OSX_ENABLED
+ if (!DirAccess::exists(data_mono_root_dir)) {
+ data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc");
+ data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib");
+ }
+#endif
- sln_filepath = ProjectSettings::get_singleton()->globalize_path(base_path + ".sln");
- csproj_filepath = ProjectSettings::get_singleton()->globalize_path(base_path + ".csproj");
#endif
}
@@ -148,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;
}
@@ -192,5 +295,28 @@ String get_project_sln_path() {
String get_project_csproj_path() {
return _GodotSharpDirs::get_singleton().csproj_filepath;
}
+
+String get_data_editor_tools_dir() {
+ return _GodotSharpDirs::get_singleton().data_editor_tools_dir;
+}
+
+String get_data_editor_prebuilt_api_dir() {
+ return _GodotSharpDirs::get_singleton().data_editor_prebuilt_api_dir;
+}
#endif
+
+String get_data_mono_etc_dir() {
+ return _GodotSharpDirs::get_singleton().data_mono_etc_dir;
+}
+
+String get_data_mono_lib_dir() {
+ return _GodotSharpDirs::get_singleton().data_mono_lib_dir;
+}
+
+#ifdef WINDOWS_ENABLED
+String get_data_mono_bin_dir() {
+ return _GodotSharpDirs::get_singleton().data_mono_bin_dir;
+}
+#endif
+
} // namespace GodotSharpDirs
diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h
index 3466cb271d..ff51888d1c 100644
--- a/modules/mono/godotsharp_dirs.h
+++ b/modules/mono/godotsharp_dirs.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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();
@@ -49,11 +50,21 @@ String get_mono_logs_dir();
#ifdef TOOLS_ENABLED
String get_mono_solutions_dir();
String get_build_logs_dir();
-String get_custom_project_settings_dir();
-#endif
String get_project_sln_path();
String get_project_csproj_path();
+
+String get_data_editor_tools_dir();
+String get_data_editor_prebuilt_api_dir();
+#endif
+
+String get_data_mono_etc_dir();
+String get_data_mono_lib_dir();
+
+#ifdef WINDOWS_ENABLED
+String get_data_mono_bin_dir();
+#endif
+
} // namespace GodotSharpDirs
#endif // GODOTSHARP_DIRS_H
diff --git a/modules/mono/icons/icon_c_#.svg b/modules/mono/icons/icon_c_#.svg
new file mode 100644
index 0000000000..69664ca553
--- /dev/null
+++ b/modules/mono/icons/icon_c_#.svg
@@ -0,0 +1,5 @@
+<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0 -1036.4)">
+<path d="m6 1046.4c-1.6569 0-3 1.3431-3 3s1.3431 3 3 3h1v-2h-1c-0.55228 0-1-0.4478-1-1 0-0.5523 0.44772-1 1-1h1v-2zm1-9-0.56445 2.2578c-0.23643 0.076-0.46689 0.1692-0.68945 0.2793l-1.9883-1.1933-1.4141 1.414 1.1953 1.9942c-0.11191 0.2211-0.20723 0.4502-0.28516 0.6855l-2.2539 0.5625v2h5.2715c-0.17677-0.3037-0.27041-0.6486-0.27148-1 9.6e-6 -1.1046 0.89543-2 2-2s2 0.8954 2 2c-4.817e-4 0.3512-0.093442 0.6961-0.26953 1h5.2695v-2l-2.2578-0.5645c-0.07594-0.2357-0.1693-0.4655-0.2793-0.6875l1.1934-1.9902-1.4141-1.414-1.9941 1.1953c-0.22113-0.1119-0.45028-0.2073-0.68555-0.2852l-0.5625-2.2539zm4 9c-0.71466-1e-4 -1.3751 0.3811-1.7324 1-0.35727 0.6188-0.35727 1.3812 0 2 0.35733 0.6189 1.0178 1.0001 1.7324 1h-2v2h2c0.71466 1e-4 1.3751-0.3811 1.7324-1 0.35727-0.6188 0.35727-1.3812 0-2-0.35733-0.6189-1.0178-1.0001-1.7324-1h2v-2z" fill="#e0e0e0"/>
+</g>
+</svg>
diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp
index f695bddfeb..a9e2136a19 100644
--- a/modules/mono/mono_gc_handle.cpp
+++ b/modules/mono/mono_gc_handle.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -65,7 +65,7 @@ Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) {
void MonoGCHandle::release() {
#ifdef DEBUG_ENABLED
- CRASH_COND(GDMono::get_singleton() == NULL);
+ CRASH_COND(!released && GDMono::get_singleton() == NULL);
#endif
if (!released && GDMono::get_singleton()->is_runtime_initialized()) {
diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h
index e5aa04e2b8..60a1eed212 100644
--- a/modules/mono/mono_gc_handle.h
+++ b/modules/mono/mono_gc_handle.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -37,7 +37,7 @@
class MonoGCHandle : public Reference {
- GDCLASS(MonoGCHandle, Reference)
+ GDCLASS(MonoGCHandle, Reference);
bool released;
bool weak;
diff --git a/modules/mono/editor/monodevelop_instance.h b/modules/mono/mono_gd/android_mono_config.h
index 29de4a4735..c5cc244aec 100644
--- a/modules/mono/editor/monodevelop_instance.h
+++ b/modules/mono/mono_gd/android_mono_config.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* monodevelop_instance.h */
+/* android_mono_config.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -28,29 +28,16 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef MONODEVELOP_INSTANCE_H
-#define MONODEVELOP_INSTANCE_H
+#ifndef ANDROID_MONO_CONFIG_H
+#define ANDROID_MONO_CONFIG_H
-#include "core/reference.h"
+#ifdef ANDROID_ENABLED
-#include "../mono_gc_handle.h"
-#include "../mono_gd/gd_mono_method.h"
+#include "core/ustring.h"
-class MonoDevelopInstance {
+// This function is defined in an auto-generated source file
+String get_godot_android_mono_config();
- Ref<MonoGCHandle> gc_handle;
- GDMonoMethod *execute_method;
+#endif // ANDROID_ENABLED
-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 // ANDROID_MONO_CONFIG_H
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 2fed6064b7..544bfc4615 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -30,10 +30,12 @@
#include "gd_mono.h"
+#include <mono/metadata/environment.h>
#include <mono/metadata/exception.h>
#include <mono/metadata/mono-config.h>
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/mono-gc.h>
+#include <mono/metadata/profiler.h>
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
@@ -42,7 +44,6 @@
#include "core/project_settings.h"
#include "../csharp_script.h"
-#include "../glue/cs_glue_version.gen.h"
#include "../godotsharp_dirs.h"
#include "../utils/path_utils.h"
#include "gd_mono_class.h"
@@ -50,19 +51,11 @@
#include "gd_mono_utils.h"
#ifdef TOOLS_ENABLED
-#include "../editor/godotsharp_editor.h"
#include "main/main.h"
#endif
-#ifdef MONO_PRINT_HANDLER_ENABLED
-void gdmono_MonoPrintCallback(const char *string, mono_bool is_stdout) {
-
- if (is_stdout) {
- OS::get_singleton()->print(string);
- } else {
- OS::get_singleton()->printerr(string);
- }
-}
+#ifdef ANDROID_ENABLED
+#include "android_mono_config.h"
#endif
GDMono *GDMono::singleton = NULL;
@@ -90,9 +83,17 @@ void setup_runtime_main_args() {
mono_runtime_set_main_args(main_args.size(), main_args.ptrw());
}
+void gdmono_profiler_init() {
+ String profiler_args = GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd");
+ bool profiler_enabled = GLOBAL_DEF("mono/profiler/enabled", false);
+ if (profiler_enabled) {
+ mono_profiler_load(profiler_args.utf8());
+ }
+}
+
#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())
@@ -102,7 +103,7 @@ static bool _wait_for_debugger_msecs(uint32_t p_msecs) {
OS::get_singleton()->delay_usec((p_msecs < 25 ? p_msecs : 25) * 1000);
- int tdiff = OS::get_singleton()->get_ticks_msec() - last_tick;
+ uint32_t tdiff = OS::get_singleton()->get_ticks_msec() - last_tick;
if (tdiff > p_msecs) {
p_msecs = 0;
@@ -118,21 +119,30 @@ void gdmono_debug_init() {
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
+ CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
+
+#ifdef TOOLS_ENABLED
int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685);
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
-#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;
+ }
+
+ 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"))
+ .utf8();
}
+#else
+ if (da_args.length() == 0)
+ return; // Exported games don't use the project settings to setup the debugger agent
#endif
- CharString 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"))
- .utf8();
// --debugger-agent=help
const char *options[] = {
"--soft-breakpoints",
@@ -145,81 +155,179 @@ void gdmono_debug_init() {
} // namespace
+void GDMono::add_mono_shared_libs_dir_to_path() {
+ // By default Mono seems to search shared libraries in the following directories:
+ // Current working directory, @executable_path@ and PATH
+ // The parent directory of the image file (assembly where the dllimport method is declared)
+ // @executable_path@/../lib
+ // @executable_path@/../Libraries (__MACH__ only)
+
+ // This does not work when embedding Mono unless we use the same directory structure.
+ // To fix this we append the directory containing our shared libraries to PATH.
+
+#if defined(WINDOWS_ENABLED) || defined(UNIX_ENABLED)
+ String path_var("PATH");
+ String path_value = OS::get_singleton()->get_environment(path_var);
+
+#ifdef WINDOWS_ENABLED
+ path_value += ';';
+
+ String bundled_bin_dir = GodotSharpDirs::get_data_mono_bin_dir();
+#ifdef TOOLS_ENABLED
+ if (DirAccess::exists(bundled_bin_dir)) {
+ path_value += bundled_bin_dir;
+ } else {
+ path_value += mono_reg_info.bin_dir;
+ }
+#else
+ if (DirAccess::exists(bundled_bin_dir))
+ path_value += bundled_bin_dir;
+#endif // TOOLS_ENABLED
+
+#else
+ path_value += ':';
+
+ String bundled_lib_dir = GodotSharpDirs::get_data_mono_lib_dir();
+ if (DirAccess::exists(bundled_lib_dir)) {
+ path_value += bundled_lib_dir;
+ } else {
+ // TODO: Do we need to add the lib dir when using the system installed Mono on Unix platforms?
+ }
+#endif // WINDOWS_ENABLED
+
+ OS::get_singleton()->set_environment(path_var, path_value);
+#endif // WINDOWS_ENABLED || UNIX_ENABLED
+}
+
void GDMono::initialize() {
ERR_FAIL_NULL(Engine::get_singleton());
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
GDMonoLog::get_singleton()->initialize();
-#ifdef MONO_PRINT_HANDLER_ENABLED
- mono_trace_set_print_handler(gdmono_MonoPrintCallback);
- mono_trace_set_printerr_handler(gdmono_MonoPrintCallback);
-#endif
+ String assembly_rootdir;
+ String config_dir;
-#ifdef WINDOWS_ENABLED
+#ifdef TOOLS_ENABLED
+#if defined(WINDOWS_ENABLED)
mono_reg_info = MonoRegUtils::find_mono();
- CharString assembly_dir;
- CharString config_dir;
-
if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) {
- assembly_dir = mono_reg_info.assembly_dir.utf8();
+ assembly_rootdir = mono_reg_info.assembly_dir;
}
if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) {
- config_dir = mono_reg_info.config_dir.utf8();
+ config_dir = mono_reg_info.config_dir;
+ }
+#elif defined(OSX_ENABLED)
+ const char *c_assembly_rootdir = mono_assembly_getrootdir();
+ const char *c_config_dir = mono_get_config_dir();
+
+ if (!c_assembly_rootdir || !c_config_dir || !DirAccess::exists(c_assembly_rootdir) || !DirAccess::exists(c_config_dir)) {
+ Vector<const char *> locations;
+ locations.push_back("/Library/Frameworks/Mono.framework/Versions/Current/");
+ 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");
+
+ if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) {
+ assembly_rootdir = hint_assembly_rootdir;
+ config_dir = hint_config_dir;
+ break;
+ }
+ }
+ }
+#endif
+#endif // TOOLS_ENABLED
+
+ String bundled_assembly_rootdir = GodotSharpDirs::get_data_mono_lib_dir();
+ String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir();
+
+#ifdef TOOLS_ENABLED
+ 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);
}
+#endif // WINDOWS_ENABLED
- mono_set_dirs(assembly_dir.length() ? assembly_dir.get_data() : NULL,
- config_dir.length() ? config_dir.get_data() : NULL);
-#elif OSX_ENABLED
- mono_set_dirs(NULL, NULL);
+#else
+ // These are always the directories in export templates
+ assembly_rootdir = bundled_assembly_rootdir;
+ config_dir = bundled_config_dir;
+#endif // TOOLS_ENABLED
+
+ // Leak if we call mono_set_dirs more than once
+ mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : NULL,
+ config_dir.length() ? config_dir.utf8().get_data() : NULL);
+
+ add_mono_shared_libs_dir_to_path();
{
- const char *assembly_rootdir = mono_assembly_getrootdir();
- const char *config_dir = mono_get_config_dir();
-
- if (!assembly_rootdir || !config_dir || !DirAccess::exists(assembly_rootdir) || !DirAccess::exists(config_dir)) {
- Vector<const char *> locations;
- locations.push_back("/Library/Frameworks/Mono.framework/Versions/Current/");
- 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");
-
- if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) {
- mono_set_dirs(hint_assembly_rootdir.utf8().get_data(), hint_config_dir.utf8().get_data());
- break;
- }
- }
+ PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/unhandled_exception_policy", PROPERTY_HINT_ENUM,
+ vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR));
+ unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP);
+ ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop);
+
+ if (Engine::get_singleton()->is_editor_hint()) {
+ // Unhandled exceptions should not terminate the editor
+ unhandled_exception_policy = POLICY_LOG_ERROR;
}
}
-#else
- mono_set_dirs(NULL, NULL);
-#endif
GDMonoAssembly::initialize();
+ gdmono_profiler_init();
+
#ifdef DEBUG_ENABLED
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);
- root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319");
+#ifndef TOOLS_ENABLED
+ // Export templates only load the Mono runtime if the project uses it
+ if (!DirAccess::exists("res://.mono"))
+ return;
+#endif
+
+#if !defined(WINDOWS_ENABLED) && !defined(NO_MONO_THREADS_SUSPEND_WORKAROUND)
+ // FIXME: Temporary workaround. See: https://github.com/godotengine/godot/issues/29812
+ if (!OS::get_singleton()->has_environment("MONO_THREADS_SUSPEND")) {
+ OS::get_singleton()->set_environment("MONO_THREADS_SUSPEND", "preemptive");
+ }
+#endif
- ERR_EXPLAIN("Mono: Failed to initialize runtime");
- ERR_FAIL_NULL(root_domain);
+ root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319");
+ ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime.");
GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread());
@@ -230,23 +338,11 @@ void GDMono::initialize() {
print_verbose("Mono: Runtime initialized");
// mscorlib assembly MUST be present at initialization
- ERR_EXPLAIN("Mono: Failed to load mscorlib assembly");
- ERR_FAIL_COND(!_load_corlib_assembly());
+ bool corlib_loaded = _load_corlib_assembly();
+ ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib 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);
+ Error domain_load_err = _load_scripts_domain();
+ ERR_FAIL_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
#ifdef DEBUG_ENABLED
bool debugger_attached = _wait_for_debugger_msecs(500);
@@ -256,96 +352,98 @@ void GDMono::initialize() {
_register_internal_calls();
- // The following assemblies are not required at initialization
-#ifdef MONO_GLUE_ENABLED
- if (_load_api_assemblies()) {
- if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) {
- // Everything is fine with the api assemblies, load the project assembly
- _load_project_assembly();
- } else {
-#ifdef TOOLS_ENABLED
- // The assembly was successfully loaded, but the full api could not be cached.
- // This is most likely an outdated assembly loaded because of an invalid version in the metadata,
- // so we invalidate the version in the metadata and unload the script domain.
+ print_verbose("Mono: INITIALIZED");
+}
- if (core_api_assembly_out_of_sync) {
- ERR_PRINT("The loaded Core API assembly is out of sync");
- metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
- } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
- ERR_PRINT("The loaded Core API assembly is in sync, but the cache update failed");
- metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
- }
+void GDMono::initialize_load_assemblies() {
- if (editor_api_assembly_out_of_sync) {
- ERR_PRINT("The loaded Editor API assembly is out of sync");
- metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
- }
+#ifndef MONO_GLUE_ENABLED
+ CRASH_NOW_MSG("Mono: This binary was built with 'mono_glue=no'; cannot load assemblies.");
+#endif
- print_line("Mono: Proceeding to unload scripts domain because of invalid API assemblies.");
+ // Load assemblies. The API and tools assemblies are required,
+ // the application is aborted if these assemblies cannot be loaded.
- Error err = _unload_scripts_domain();
- if (err != OK) {
- WARN_PRINT("Mono: Failed to unload scripts domain");
- }
-#else
- ERR_PRINT("The loaded API assembly is invalid");
- CRASH_NOW();
+ _load_api_assemblies();
+
+#if defined(TOOLS_ENABLED)
+ bool tool_assemblies_loaded = _load_tools_assemblies();
+ CRASH_COND_MSG(!tool_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies.");
#endif
- }
+
+ // Load the project's main assembly. This doesn't necessarily need to succeed.
+ // The game may not be using .NET at all, or if the project does use .NET and
+ // we're running in the editor, it may just happen to be it wasn't built yet.
+ if (!_load_project_assembly()) {
+ if (OS::get_singleton()->is_stdout_verbose())
+ print_error("Mono: Failed to load project assembly");
}
-#else
- print_verbose("Mono: Glue disabled, ignoring script assemblies.");
-#endif
+}
- print_verbose("Mono: INITIALIZED");
+bool GDMono::_are_api_assemblies_out_of_sync() {
+ bool out_of_sync = core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
+#ifdef TOOLS_ENABLED
+ if (!out_of_sync)
+ out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync;
+#endif
+ return out_of_sync;
}
-#ifdef MONO_GLUE_ENABLED
namespace GodotSharpBindings {
+#ifdef MONO_GLUE_ENABLED
uint64_t get_core_api_hash();
#ifdef TOOLS_ENABLED
uint64_t get_editor_api_hash();
-#endif // TOOLS_ENABLED
+#endif
uint32_t get_bindings_version();
+uint32_t get_cs_glue_version();
void register_generated_icalls();
-} // namespace GodotSharpBindings
-#endif
-void GDMono::_register_internal_calls() {
-#ifdef MONO_GLUE_ENABLED
- GodotSharpBindings::register_generated_icalls();
-#endif
+#else
+uint64_t get_core_api_hash() {
+ GD_UNREACHABLE();
+}
#ifdef TOOLS_ENABLED
- GodotSharpEditor::register_internal_calls();
+uint64_t get_editor_api_hash() {
+ GD_UNREACHABLE();
+}
#endif
+uint32_t get_bindings_version() {
+ GD_UNREACHABLE();
+}
+uint32_t get_cs_glue_version() {
+ GD_UNREACHABLE();
}
-#ifdef DEBUG_METHODS_ENABLED
-void GDMono::_initialize_and_check_api_hashes() {
+void register_generated_icalls() {
+ /* Fine, just do nothing */
+}
- api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE);
+#endif // MONO_GLUE_ENABLED
+} // namespace GodotSharpBindings
+
+void GDMono::_register_internal_calls() {
+ GodotSharpBindings::register_generated_icalls();
+}
+void GDMono::_initialize_and_check_api_hashes() {
#ifdef MONO_GLUE_ENABLED
- if (api_core_hash != GodotSharpBindings::get_core_api_hash()) {
- ERR_PRINT("Mono: Core API hash mismatch!");
+#ifdef DEBUG_METHODS_ENABLED
+ if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) {
+ ERR_PRINT("Mono: Core API hash mismatch.");
}
-#endif
#ifdef TOOLS_ENABLED
- api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR);
-
-#ifdef MONO_GLUE_ENABLED
- if (api_editor_hash != GodotSharpBindings::get_editor_api_hash()) {
- ERR_PRINT("Mono: Editor API hash mismatch!");
+ if (get_api_editor_hash() != GodotSharpBindings::get_editor_api_hash()) {
+ ERR_PRINT("Mono: Editor API hash mismatch.");
}
-#endif
-
#endif // TOOLS_ENABLED
-}
#endif // DEBUG_METHODS_ENABLED
+#endif // MONO_GLUE_ENABLED
+}
void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) {
@@ -399,6 +497,32 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo
return true;
}
+bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly) {
+
+ CRASH_COND(!r_assembly);
+
+ print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
+
+ GDMonoAssembly *assembly = GDMonoAssembly::load_from(p_name, p_path, p_refonly);
+
+ if (!assembly)
+ return false;
+
+#ifdef DEBUG_ENABLED
+ uint32_t domain_id = mono_domain_get_id(mono_domain_get());
+ GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
+
+ ERR_FAIL_COND_V(stored_assembly == NULL, false);
+ ERR_FAIL_COND_V(*stored_assembly != assembly, false);
+#endif
+
+ *r_assembly = assembly;
+
+ print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());
+
+ return true;
+}
+
APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) {
APIAssembly::Version api_assembly_version;
@@ -442,26 +566,139 @@ bool GDMono::_load_corlib_assembly() {
return success;
}
+#ifdef TOOLS_ENABLED
+bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) {
+
+ bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ?
+ GDMono::get_singleton()->core_api_assembly_out_of_sync :
+ GDMono::get_singleton()->editor_api_assembly_out_of_sync;
+
+ String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
+ String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
+
+ String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
+
+ // Create destination directory if needed
+ if (!DirAccess::exists(dst_dir)) {
+ DirAccess *da = DirAccess::create_for_path(dst_dir);
+ Error err = da->make_dir_recursive(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 = assembly_name + ".dll";
+ String assembly_src = src_dir.plus_file(assembly_file);
+ String assembly_dst = dst_dir.plus_file(assembly_file);
+
+ if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) {
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ String xml_file = assembly_name + ".xml";
+ if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
+ WARN_PRINTS("Failed to copy '" + xml_file + "'.");
+
+ String pdb_file = assembly_name + ".pdb";
+ if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
+ WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
+
+ Error err = da->copy(assembly_src, assembly_dst);
+
+ if (err != OK) {
+ ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
+ return false;
+ }
+
+ api_assembly_out_of_sync = false;
+ }
+
+ return true;
+}
+
+String GDMono::update_api_assemblies_from_prebuilt() {
+
+#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \
+ ( \
+ (m_out_of_sync ? \
+ String("The assembly is invalidated ") : \
+ String("The assembly was not found ")) + \
+ (m_prebuilt_exists ? \
+ String("and the prebuilt assemblies are missing.") : \
+ String("and we failed to copy the prebuilt assemblies.")))
+
+ bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync;
+
+ String core_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+ String editor_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+
+ if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
+ return String(); // No update needed
+
+ const int CONFIGS_LEN = 2;
+ String configs[CONFIGS_LEN] = { String("Debug"), String("Release") };
+
+ for (int i = 0; i < CONFIGS_LEN; i++) {
+ String config = configs[i];
+
+ print_verbose("Updating '" + config + "' API assemblies");
+
+ String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config);
+ String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+ String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+
+ if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
+ return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false);
+ }
+
+ // Copy the prebuilt Api
+ if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) ||
+ !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) {
+ return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true);
+ }
+ }
+
+ return String(); // Updated successfully
+
+#undef FAIL_REASON
+}
+#endif
+
bool GDMono::_load_core_api_assembly() {
if (core_api_assembly)
return true;
#ifdef TOOLS_ENABLED
- if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE))
- return false;
-#endif
+ // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
+
+ // If running the project manager, load it from the prebuilt API directory
+ String assembly_dir = !Main::is_project_manager() ?
+ GodotSharpDirs::get_res_assemblies_dir() :
+ GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
- bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly);
+ String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+
+ bool success = FileAccess::exists(assembly_path) &&
+ load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly);
+#else
+ bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
+#endif
if (success) {
-#ifdef MONO_GLUE_ENABLED
APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE);
core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash ||
GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
- CS_GLUE_VERSION != api_assembly_ver.cs_glue_version;
-#endif
- GDMonoUtils::update_godot_api_cache();
+ GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
+ if (!core_api_assembly_out_of_sync) {
+ GDMonoUtils::update_godot_api_cache();
+
+ _install_trace_listener();
+ }
+ } else {
+ core_api_assembly_out_of_sync = false;
}
return success;
@@ -473,142 +710,160 @@ bool GDMono::_load_editor_api_assembly() {
if (editor_api_assembly)
return true;
-#ifdef TOOLS_ENABLED
- if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR))
- return false;
-#endif
+ // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
+
+ // If running the project manager, load it from the prebuilt API directory
+ String assembly_dir = !Main::is_project_manager() ?
+ GodotSharpDirs::get_res_assemblies_dir() :
+ GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
- bool success = load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
+ String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+
+ bool success = FileAccess::exists(assembly_path) &&
+ load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly);
if (success) {
-#ifdef MONO_GLUE_ENABLED
APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR);
editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash ||
GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
- CS_GLUE_VERSION != api_assembly_ver.cs_glue_version;
-#endif
+ GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
+ } else {
+ editor_api_assembly_out_of_sync = false;
}
return success;
}
#endif
+bool GDMono::_try_load_api_assemblies() {
+
+ if (!_load_core_api_assembly()) {
+ if (OS::get_singleton()->is_stdout_verbose())
+ print_error("Mono: Failed to load Core API assembly");
+ return false;
+ }
+
#ifdef TOOLS_ENABLED
-bool GDMono::_load_editor_tools_assembly() {
+ if (!_load_editor_api_assembly()) {
+ if (OS::get_singleton()->is_stdout_verbose())
+ print_error("Mono: Failed to load Editor API assembly");
+ return false;
+ }
- if (editor_tools_assembly)
- return true;
+ if (editor_api_assembly_out_of_sync)
+ return false;
+#endif
- _GDMONO_SCOPE_DOMAIN_(tools_domain)
+ // Check if the core API assembly is out of sync only after trying to load the
+ // editor API assembly. Otherwise, if both assemblies are out of sync, we would
+ // only update the former as we won't know the latter also needs to be updated.
+ if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)
+ return false;
- return load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly);
+ return true;
}
-#endif
-bool GDMono::_load_project_assembly() {
+void GDMono::_load_api_assemblies() {
- if (project_assembly)
- return true;
+ bool api_assemblies_loaded = _try_load_api_assemblies();
- String name = ProjectSettings::get_singleton()->get("application/config/name");
- if (name.empty()) {
- name = "UnnamedProject";
- }
+ if (!api_assemblies_loaded) {
+#ifdef TOOLS_ENABLED
+ // The API assemblies are out of sync. Fine, try one more time, but this time
+ // update them from the prebuilt assemblies directory before trying to load them.
- bool success = load_assembly(name, &project_assembly);
+ // Shouldn't happen. The project manager loads the prebuilt API assemblies
+ CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies.");
- if (success) {
- mono_assembly_set_main(project_assembly->get_assembly());
- } else {
- if (OS::get_singleton()->is_stdout_verbose())
- print_error("Mono: Failed to load project assembly");
- }
+ // 1. Unload the scripts domain
+ Error domain_unload_err = _unload_scripts_domain();
+ CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain.");
- return success;
-}
+ // 2. Update the API assemblies
+ String update_error = update_api_assemblies_from_prebuilt();
+ CRASH_COND_MSG(!update_error.empty(), update_error);
-bool GDMono::_load_api_assemblies() {
+ // 3. Load the scripts domain again
+ Error domain_load_err = _load_scripts_domain();
+ CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
- if (!_load_core_api_assembly()) {
- if (OS::get_singleton()->is_stdout_verbose())
- print_error("Mono: Failed to load Core API assembly");
- return false;
- } else {
-#ifdef TOOLS_ENABLED
- if (!_load_editor_api_assembly()) {
- if (OS::get_singleton()->is_stdout_verbose())
- print_error("Mono: Failed to load Editor API assembly");
- return false;
- }
+ // 4. Try loading the updated assemblies
+ api_assemblies_loaded = _try_load_api_assemblies();
#endif
}
- return true;
-}
+ if (!api_assemblies_loaded) {
+ // welp... too bad
+
+ if (_are_api_assemblies_out_of_sync()) {
+ if (core_api_assembly_out_of_sync) {
+ ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync.");
+ } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
+ ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed.");
+ }
#ifdef TOOLS_ENABLED
-String GDMono::_get_api_assembly_metadata_path() {
+ if (editor_api_assembly_out_of_sync) {
+ ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync.");
+ }
+#endif
- return GodotSharpDirs::get_res_metadata_dir().plus_file("api_assemblies.cfg");
+ CRASH_NOW();
+ } else {
+ CRASH_NOW_MSG("Failed to load one of the API assemblies.");
+ }
+ }
}
-void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated) {
+#ifdef TOOLS_ENABLED
+bool GDMono::_load_tools_assemblies() {
- String section = APIAssembly::to_string(p_api_type);
- String path = _get_api_assembly_metadata_path();
+ if (tools_assembly && tools_project_editor_assembly)
+ return true;
- Ref<ConfigFile> metadata;
- metadata.instance();
- metadata->load(path);
+ bool success = load_assembly(TOOLS_ASM_NAME, &tools_assembly) &&
+ load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_assembly);
- metadata->set_value(section, "invalidated", p_invalidated);
+ return success;
+}
+#endif
- String assembly_path = GodotSharpDirs::get_res_assemblies_dir()
- .plus_file(p_api_type == APIAssembly::API_CORE ?
- API_ASSEMBLY_NAME ".dll" :
- EDITOR_API_ASSEMBLY_NAME ".dll");
+bool GDMono::_load_project_assembly() {
- ERR_FAIL_COND(!FileAccess::exists(assembly_path));
+ if (project_assembly)
+ return true;
- uint64_t modified_time = FileAccess::get_modified_time(assembly_path);
+ 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";
+ }
- metadata->set_value(section, "invalidated_asm_modified_time", String::num_uint64(modified_time));
+ bool success = load_assembly(appname_safe, &project_assembly);
- String dir = path.get_base_dir();
- if (!DirAccess::exists(dir)) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND(!da);
- Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(dir));
- ERR_FAIL_COND(err != OK);
+ if (success) {
+ mono_assembly_set_main(project_assembly->get_assembly());
}
- Error save_err = metadata->save(path);
- ERR_FAIL_COND(save_err != OK);
+ return success;
}
-bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) {
-
- String section = APIAssembly::to_string(p_api_type);
-
- Ref<ConfigFile> metadata;
- metadata.instance();
- metadata->load(_get_api_assembly_metadata_path());
-
- String assembly_path = GodotSharpDirs::get_res_assemblies_dir()
- .plus_file(p_api_type == APIAssembly::API_CORE ?
- API_ASSEMBLY_NAME ".dll" :
- EDITOR_API_ASSEMBLY_NAME ".dll");
+void GDMono::_install_trace_listener() {
- if (!FileAccess::exists(assembly_path))
- return false;
-
- uint64_t modified_time = FileAccess::get_modified_time(assembly_path);
-
- uint64_t stored_modified_time = metadata->get_value(section, "invalidated_asm_modified_time", 0);
-
- return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time;
-}
+#ifdef DEBUG_ENABLED
+ // Install the trace listener now before the project assembly is loaded
+ typedef void (*DebuggingUtils_InstallTraceListener)(MonoObject **);
+ MonoException *exc = NULL;
+ GDMonoClass *debug_utils = core_api_assembly->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
+ DebuggingUtils_InstallTraceListener install_func =
+ (DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener");
+ install_func((MonoObject **)&exc);
+ if (exc) {
+ ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ }
#endif
+}
Error GDMono::_load_scripts_domain() {
@@ -618,8 +873,7 @@ Error GDMono::_load_scripts_domain() {
scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain");
- ERR_EXPLAIN("Mono: Could not create scripts app domain");
- ERR_FAIL_NULL_V(scripts_domain, ERR_CANT_CREATE);
+ ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain.");
mono_domain_set(scripts_domain, true);
@@ -632,38 +886,39 @@ Error GDMono::_unload_scripts_domain() {
print_verbose("Mono: Unloading scripts domain...");
- _GodotSharp::get_singleton()->_dispose_callback();
-
if (mono_domain_get() != root_domain)
mono_domain_set(root_domain, true);
- mono_gc_collect(mono_gc_max_generation());
+ finalizing_scripts_domain = true;
- mono_domain_finalize(scripts_domain, 2000);
+ if (!mono_domain_finalize(scripts_domain, 2000)) {
+ ERR_PRINT("Mono: Domain finalization timeout.");
+ }
+
+ finalizing_scripts_domain = false;
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;
- editor_api_assembly_out_of_sync = false;
-
MonoDomain *domain = scripts_domain;
scripts_domain = NULL;
- _GodotSharp::get_singleton()->_dispose_callback();
-
MonoException *exc = NULL;
mono_domain_try_unload(domain, (MonoObject **)&exc);
if (exc) {
- ERR_PRINT("Exception thrown when unloading scripts domain");
+ ERR_PRINT("Exception thrown when unloading scripts domain.");
GDMonoUtils::debug_unhandled_exception(exc);
return FAILED;
}
@@ -671,80 +926,38 @@ 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 TOOLS_ENABLED
+#ifdef GD_MONO_HOT_RELOAD
Error GDMono::reload_scripts_domain() {
ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
if (scripts_domain) {
- Error err = _unload_scripts_domain();
- if (err != OK) {
- ERR_PRINT("Mono: Failed to unload scripts domain");
- return err;
- }
+ Error domain_unload_err = _unload_scripts_domain();
+ ERR_FAIL_COND_V_MSG(domain_unload_err != OK, domain_unload_err, "Mono: Failed to unload scripts domain.");
}
- Error err = _load_scripts_domain();
- if (err != OK) {
- ERR_PRINT("Mono: Failed to load scripts domain");
- return err;
- }
+ CSharpLanguage::get_singleton()->_on_scripts_domain_unloaded();
-#ifdef MONO_GLUE_ENABLED
- if (!_load_api_assemblies()) {
- return ERR_CANT_OPEN;
- }
+ Error domain_load_err = _load_scripts_domain();
+ ERR_FAIL_COND_V_MSG(domain_load_err != OK, domain_load_err, "Mono: Failed to load scripts domain.");
- if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) {
- // Everything is fine with the api assemblies, load the project assembly
- _load_project_assembly();
- } else {
- // The assembly was successfully loaded, but the full api could not be cached.
- // This is most likely an outdated assembly loaded because of an invalid version in the metadata,
- // so we invalidate the version in the metadata and unload the script domain.
-
- if (core_api_assembly_out_of_sync) {
- ERR_PRINT("The loaded Core API assembly is out of sync");
- metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
- } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
- ERR_PRINT("The loaded Core API assembly is in sync, but the cache update failed");
- metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
- }
-
- if (editor_api_assembly_out_of_sync) {
- ERR_PRINT("The loaded Editor API assembly is out of sync");
- metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
- }
+ // Load assemblies. The API and tools assemblies are required,
+ // the application is aborted if these assemblies cannot be loaded.
- Error err = _unload_scripts_domain();
- if (err != OK) {
- WARN_PRINT("Mono: Failed to unload scripts domain");
- }
+ _load_api_assemblies();
- return ERR_CANT_RESOLVE;
- }
+#if defined(TOOLS_ENABLED)
+ bool tools_assemblies_loaded = _load_tools_assemblies();
+ CRASH_COND_MSG(!tools_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies.");
+#endif
- if (!_load_project_assembly())
+ // Load the project's main assembly. Here, during hot-reloading, we do
+ // consider failing to load the project's main assembly to be an error.
+ // However, unlike the API and tools assemblies, the application can continue working.
+ if (!_load_project_assembly()) {
+ print_error("Mono: Failed to load project assembly");
return ERR_CANT_OPEN;
-#else
- print_verbose("Mono: Glue disabled, ignoring script assemblies.");
-#endif
+ }
return OK;
}
@@ -753,16 +966,19 @@ 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);
- print_verbose("Mono: Unloading domain `" + domain_name + "`...");
+ print_verbose("Mono: Unloading domain '" + domain_name + "'...");
- if (mono_domain_get() != root_domain)
+ if (mono_domain_get() == p_domain)
mono_domain_set(root_domain, true);
- mono_gc_collect(mono_gc_max_generation());
- mono_domain_finalize(p_domain, 2000);
+ if (!mono_domain_finalize(p_domain, 2000)) {
+ ERR_PRINT("Mono: Domain finalization timeout.");
+ }
+
mono_gc_collect(mono_gc_max_generation());
_domain_assemblies_cleanup(mono_domain_get_id(p_domain));
@@ -771,8 +987,8 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
mono_domain_try_unload(p_domain, (MonoObject **)&exc);
if (exc) {
- ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`");
- GDMonoUtils::debug_unhandled_exception(exc);
+ ERR_PRINTS("Exception thrown when unloading domain '" + domain_name + "'.");
+ GDMonoUtils::debug_print_unhandled_exception(exc);
return FAILED;
}
@@ -803,6 +1019,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];
@@ -826,8 +1058,10 @@ void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) {
if (ScriptDebugger::get_singleton())
ScriptDebugger::get_singleton()->idle_poll();
#endif
- abort();
- _UNREACHABLE_();
+
+ exit(mono_environment_exitcode_get());
+
+ GD_UNREACHABLE();
}
GDMono::GDMono() {
@@ -837,41 +1071,40 @@ GDMono::GDMono() {
gdmono_log = memnew(GDMonoLog);
runtime_initialized = false;
+ finalizing_scripts_domain = false;
root_domain = NULL;
scripts_domain = NULL;
-#ifdef TOOLS_ENABLED
- tools_domain = NULL;
-#endif
core_api_assembly_out_of_sync = false;
+#ifdef TOOLS_ENABLED
editor_api_assembly_out_of_sync = false;
+#endif
corlib_assembly = NULL;
core_api_assembly = NULL;
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
- editor_tools_assembly = NULL;
+ tools_assembly = NULL;
+ tools_project_editor_assembly = NULL;
#endif
-#ifdef DEBUG_METHODS_ENABLED
api_core_hash = 0;
#ifdef TOOLS_ENABLED
api_editor_hash = 0;
#endif
-#endif
+
+ unhandled_exception_policy = POLICY_TERMINATE_APP;
}
GDMono::~GDMono() {
if (is_runtime_initialized()) {
-
if (scripts_domain) {
-
Error err = _unload_scripts_domain();
if (err != OK) {
- WARN_PRINT("Mono: Failed to unload scripts domain");
+ ERR_PRINT("Mono: Failed to unload scripts domain.");
}
}
@@ -886,12 +1119,12 @@ GDMono::~GDMono() {
}
assemblies.clear();
- GDMonoUtils::clear_cache();
-
print_verbose("Mono: Runtime cleanup...");
mono_jit_cleanup(root_domain);
+ print_verbose("Mono: Finalized");
+
runtime_initialized = false;
}
@@ -903,29 +1136,6 @@ GDMono::~GDMono() {
_GodotSharp *_GodotSharp::singleton = NULL;
-void _GodotSharp::_dispose_callback() {
-
-#ifndef NO_THREADS
- queue_mutex->lock();
-#endif
-
- for (List<NodePath *>::Element *E = np_delete_queue.front(); E; E = E->next()) {
- memdelete(E->get());
- }
-
- for (List<RID *>::Element *E = rid_delete_queue.front(); E; E = E->next()) {
- memdelete(E->get());
- }
-
- np_delete_queue.clear();
- rid_delete_queue.clear();
- queue_empty = true;
-
-#ifndef NO_THREADS
- queue_mutex->unlock();
-#endif
-}
-
void _GodotSharp::attach_thread() {
GDMonoUtils::attach_current_thread();
@@ -945,14 +1155,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) {
@@ -974,6 +1184,8 @@ bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) {
if (!p_domain)
return true;
+ 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);
}
@@ -987,47 +1199,10 @@ bool _GodotSharp::is_runtime_initialized() {
return GDMono::get_singleton()->is_runtime_initialized();
}
-#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \
- m_queue.push_back(m_inst); \
- if (queue_empty) { \
- queue_empty = false; \
- if (!is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) { /* call_deferred may not be safe here */ \
- call_deferred("_dispose_callback"); \
- } \
- }
-
-void _GodotSharp::queue_dispose(NodePath *p_node_path) {
-
- if (GDMonoUtils::is_main_thread() && !is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) {
- memdelete(p_node_path);
- } else {
-#ifndef NO_THREADS
- queue_mutex->lock();
-#endif
-
- ENQUEUE_FOR_DISPOSAL(np_delete_queue, p_node_path);
-
-#ifndef NO_THREADS
- queue_mutex->unlock();
-#endif
- }
-}
-
-void _GodotSharp::queue_dispose(RID *p_rid) {
-
- if (GDMonoUtils::is_main_thread() && !is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) {
- memdelete(p_rid);
- } else {
-#ifndef NO_THREADS
- queue_mutex->lock();
-#endif
-
- ENQUEUE_FOR_DISPOSAL(rid_delete_queue, p_rid);
-
-#ifndef NO_THREADS
- queue_mutex->unlock();
+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() {
@@ -1042,24 +1217,15 @@ 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("_dispose_callback"), &_GodotSharp::_dispose_callback);
+ ClassDB::bind_method(D_METHOD("_reload_assemblies"), &_GodotSharp::_reload_assemblies);
}
_GodotSharp::_GodotSharp() {
singleton = this;
- queue_empty = true;
-#ifndef NO_THREADS
- queue_mutex = Mutex::create();
-#endif
}
_GodotSharp::~_GodotSharp() {
singleton = NULL;
-
- if (queue_mutex) {
- memdelete(queue_mutex);
- }
}
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 0c5503d28e..343d68bc2d 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -78,93 +78,104 @@ 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 {
+public:
+ enum UnhandledExceptionPolicy {
+ POLICY_TERMINATE_APP,
+ POLICY_LOG_ERROR
+ };
+
+private:
bool runtime_initialized;
bool finalizing_scripts_domain;
MonoDomain *root_domain;
MonoDomain *scripts_domain;
-#ifdef TOOLS_ENABLED
- MonoDomain *tools_domain;
-#endif
bool core_api_assembly_out_of_sync;
+#ifdef TOOLS_ENABLED
bool editor_api_assembly_out_of_sync;
+#endif
GDMonoAssembly *corlib_assembly;
GDMonoAssembly *core_api_assembly;
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;
+ UnhandledExceptionPolicy unhandled_exception_policy;
+
void _domain_assemblies_cleanup(uint32_t p_domain_id);
+ bool _are_api_assemblies_out_of_sync();
+
bool _load_corlib_assembly();
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();
- bool _load_api_assemblies();
+ bool _try_load_api_assemblies();
+ void _load_api_assemblies();
-#ifdef TOOLS_ENABLED
- String _get_api_assembly_metadata_path();
-#endif
+ void _install_trace_listener();
void _register_internal_calls();
Error _load_scripts_domain();
Error _unload_scripts_domain();
-#ifdef TOOLS_ENABLED
- Error _load_tools_domain();
-#endif
-
-#ifdef DEBUG_METHODS_ENABLED
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
uint64_t api_editor_hash;
#endif
void _initialize_and_check_api_hashes();
-#endif
GDMonoLog *gdmono_log;
-#ifdef WINDOWS_ENABLED
+#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
MonoRegInfo mono_reg_info;
#endif
+ void add_mono_shared_libs_dir_to_path();
+
protected:
static GDMono *singleton;
public:
#ifdef DEBUG_METHODS_ENABLED
- uint64_t get_api_core_hash() { return api_core_hash; }
+ uint64_t get_api_core_hash() {
+ if (api_core_hash == 0)
+ api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE);
+ return api_core_hash;
+ }
#ifdef TOOLS_ENABLED
- uint64_t get_api_editor_hash() { return api_editor_hash; }
-#endif
-#endif
+ uint64_t get_api_editor_hash() {
+ if (api_editor_hash == 0)
+ api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR);
+ return api_editor_hash;
+ }
+#endif // TOOLS_ENABLED
+#endif // DEBUG_METHODS_ENABLED
#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);
+ bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config);
+ String update_api_assemblies_from_prebuilt();
#endif
static GDMono *get_singleton() { return singleton; }
- static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data);
+ GD_NORETURN static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data);
+
+ UnhandledExceptionPolicy get_unhandled_exception_policy() const { return unhandled_exception_policy; }
// Do not use these, unless you know what you're doing
void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly);
@@ -172,45 +183,51 @@ public:
_FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; }
+ _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
-#ifdef WINDOWS_ENABLED
+#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
const MonoRegInfo &get_mono_reg_info() { return mono_reg_info; }
#endif
GDMonoClass *get_class(MonoClass *p_raw_class);
+ GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
-#ifdef TOOLS_ENABLED
+#ifdef GD_MONO_HOT_RELOAD
Error reload_scripts_domain();
#endif
bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
+ bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false);
+
Error finalize_and_unload_domain(MonoDomain *p_domain);
void initialize();
+ void initialize_load_assemblies();
GDMono();
~GDMono();
};
-class GDMonoScopeDomain {
+namespace gdmono {
+
+class ScopeDomain {
MonoDomain *prev_domain;
public:
- GDMonoScopeDomain(MonoDomain *p_domain) {
+ ScopeDomain(MonoDomain *p_domain) {
MonoDomain *prev_domain = mono_domain_get();
if (prev_domain != p_domain) {
this->prev_domain = prev_domain;
@@ -220,33 +237,47 @@ public:
}
}
- ~GDMonoScopeDomain() {
+ ~ScopeDomain() {
if (prev_domain)
mono_domain_set(prev_domain, false);
}
};
-#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \
- GDMonoScopeDomain __gdmono__scope__domain__(m_mono_domain); \
+class ScopeExitDomainUnload {
+ MonoDomain *domain;
+
+public:
+ ScopeExitDomainUnload(MonoDomain *p_domain) :
+ domain(p_domain) {
+ }
+
+ ~ScopeExitDomainUnload() {
+ if (domain)
+ GDMono::get_singleton()->finalize_and_unload_domain(domain);
+ }
+};
+
+} // namespace gdmono
+
+#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \
+ gdmono::ScopeDomain __gdmono__scope__domain__(m_mono_domain); \
(void)__gdmono__scope__domain__;
+#define _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(m_mono_domain) \
+ gdmono::ScopeExitDomainUnload __gdmono__scope__exit__domain__unload__(m_mono_domain); \
+ (void)__gdmono__scope__exit__domain__unload__;
+
class _GodotSharp : public Object {
- GDCLASS(_GodotSharp, Object)
+ GDCLASS(_GodotSharp, Object);
friend class GDMono;
- void _dispose_callback();
-
bool _is_domain_finalizing_for_unload(int32_t p_domain_id);
List<NodePath *> np_delete_queue;
List<RID *> rid_delete_queue;
- bool queue_empty;
-
-#ifndef NO_THREADS
- Mutex *queue_mutex;
-#endif
+ void _reload_assemblies(bool p_soft_reload);
protected:
static _GodotSharp *singleton;
@@ -270,9 +301,6 @@ public:
bool is_runtime_shutting_down();
bool is_runtime_initialized();
- void queue_dispose(NodePath *p_node_path);
- void queue_dispose(RID *p_rid);
-
_GodotSharp();
~_GodotSharp();
};
diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp
index 27ce39b6d7..a82bb42731 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -46,6 +46,46 @@ bool GDMonoAssembly::in_preload = false;
Vector<String> GDMonoAssembly::search_dirs;
+void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) {
+
+ String framework_dir;
+
+ if (!p_custom_bcl_dir.empty()) {
+ 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"));
+ }
+
+ if (p_custom_config.length()) {
+ r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(p_custom_config));
+ } else {
+ r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
+ }
+
+ if (p_custom_config.empty()) {
+ r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir());
+ } else {
+ String api_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_base_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
+}
+
void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
if (no_search)
@@ -93,35 +133,7 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d
no_search = true; // Avoid the recursion madness
- String path;
- GDMonoAssembly *res = NULL;
-
- for (int i = 0; i < search_dirs.size(); i++) {
- const String &search_dir = search_dirs[i];
-
- if (has_extension) {
- path = search_dir.plus_file(name);
- if (FileAccess::exists(path)) {
- res = _load_assembly_from(name.get_basename(), path, refonly);
- if (res != NULL)
- break;
- }
- } else {
- path = search_dir.plus_file(name + ".dll");
- if (FileAccess::exists(path)) {
- res = _load_assembly_from(name, path, refonly);
- if (res != NULL)
- break;
- }
-
- path = search_dir.plus_file(name + ".exe");
- if (FileAccess::exists(path)) {
- res = _load_assembly_from(name, path, refonly);
- if (res != NULL)
- break;
- }
- }
- }
+ GDMonoAssembly *res = _load_assembly_search(name, search_dirs, refonly);
no_search = false;
@@ -130,42 +142,23 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d
static _THREAD_LOCAL_(MonoImage *) image_corlib_loading = NULL;
-MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly) {
+MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
(void)user_data; // UNUSED
if (search_dirs.empty()) {
- search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
- search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir());
- search_dirs.push_back(OS::get_singleton()->get_resource_dir());
- search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir());
-#ifdef GD_MONO_EDITOR_ASSEMBLIES_DIR
- search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir().plus_file(_MKSTR(GD_MONO_EDITOR_ASSEMBLIES_DIR)).simplify_path());
-#endif
-
- const char *rootdir = mono_assembly_getrootdir();
- if (rootdir) {
- search_dirs.push_back(String(rootdir).plus_file("mono").plus_file("4.5"));
- search_dirs.push_back(String(rootdir).plus_file("mono").plus_file("4.5").plus_file("Facades"));
- }
-
- if (assemblies_path) {
- while (*assemblies_path) {
- search_dirs.push_back(*assemblies_path);
- ++assemblies_path;
- }
- }
+ fill_search_dirs(search_dirs);
}
{
- // If we find the assembly here, we load it with `mono_assembly_load_from_full`,
+ // If we find the assembly here, we load it with 'mono_assembly_load_from_full',
// which in turn invokes load hooks before returning the MonoAssembly to us.
- // One of the load hooks is `load_aot_module`. This hook can end up calling preload hooks
- // again for the same assembly in certain in certain circumstances (the `do_load_image` part).
+ // One of the load hooks is 'load_aot_module'. This hook can end up calling preload hooks
+ // again for the same assembly in certain in certain circumstances (the 'do_load_image' part).
// If this is the case and we return NULL due to the no_search condition below,
// it will result in an internal crash later on. Therefore we need to return the assembly we didn't
- // get yet from `mono_assembly_load_from_full`. Luckily we have the image, which already got it.
- // This must be done here. If done in search hooks, it would cause `mono_assembly_load_from_full`
+ // get yet from 'mono_assembly_load_from_full'. Luckily we have the image, which already got it.
+ // This must be done here. If done in search hooks, it would cause 'mono_assembly_load_from_full'
// to think another MonoAssembly for this assembly was already loaded, making it delete its own,
// when in fact both pointers were the same... This hooks thing is confusing.
if (image_corlib_loading) {
@@ -188,27 +181,7 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **asse
if (stored_assembly)
return (*stored_assembly)->get_assembly();
- String path;
-
- for (int i = 0; i < search_dirs.size(); i++) {
- const String &search_dir = search_dirs[i];
-
- if (has_extension) {
- path = search_dir.plus_file(name);
- if (FileAccess::exists(path)) {
- res = _load_assembly_from(name.get_basename(), path, refonly);
- if (res != NULL)
- break;
- }
- } else {
- path = search_dir.plus_file(name + ".dll");
- if (FileAccess::exists(path)) {
- res = _load_assembly_from(name, path, refonly);
- if (res != NULL)
- break;
- }
- }
- }
+ res = _load_assembly_search("mscorlib.dll", search_dirs, refonly);
}
no_search = false;
@@ -217,6 +190,43 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **asse
return res ? res->get_assembly() : NULL;
}
+GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
+
+ GDMonoAssembly *res = NULL;
+ String path;
+
+ bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
+
+ for (int i = 0; i < p_search_dirs.size(); i++) {
+ const String &search_dir = p_search_dirs[i];
+
+ if (has_extension) {
+ path = search_dir.plus_file(p_name);
+ if (FileAccess::exists(path)) {
+ res = _load_assembly_from(p_name.get_basename(), path, p_refonly);
+ if (res != NULL)
+ return res;
+ }
+ } else {
+ path = search_dir.plus_file(p_name + ".dll");
+ if (FileAccess::exists(path)) {
+ res = _load_assembly_from(p_name, path, p_refonly);
+ if (res != NULL)
+ return res;
+ }
+
+ path = search_dir.plus_file(p_name + ".exe");
+ if (FileAccess::exists(path)) {
+ res = _load_assembly_from(p_name, path, p_refonly);
+ if (res != NULL)
+ return res;
+ }
+ }
+ }
+
+ return NULL;
+}
+
GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly) {
GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path));
@@ -271,7 +281,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;
@@ -284,6 +305,7 @@ Error GDMonoAssembly::load(bool p_refonly) {
ERR_FAIL_NULL_V(image, ERR_FILE_CANT_OPEN);
#ifdef DEBUG_ENABLED
+ Vector<uint8_t> pdb_data;
String pdb_path(path + ".pdb");
if (!FileAccess::exists(pdb_path)) {
@@ -293,8 +315,9 @@ Error GDMonoAssembly::load(bool p_refonly) {
goto no_pdb;
}
- pdb_data.clear();
pdb_data = FileAccess::get_file_as_array(pdb_path);
+
+ // mono_debug_close_image doesn't seem to be needed
mono_debug_open_image_from_memory(image, &pdb_data[0], pdb_data.size());
no_pdb:
@@ -313,6 +336,9 @@ no_pdb:
ERR_FAIL_COND_V(status != MONO_IMAGE_OK || assembly == NULL, ERR_FILE_CANT_OPEN);
+ // Decrement refcount which was previously incremented by mono_image_open_from_data_with_name
+ mono_image_close(image);
+
loaded = true;
modified_time = last_modified_time;
@@ -328,8 +354,6 @@ Error GDMonoAssembly::wrapper_for_image(MonoImage *p_image) {
image = p_image;
- mono_image_addref(image);
-
loaded = true;
return OK;
@@ -339,13 +363,6 @@ void GDMonoAssembly::unload() {
ERR_FAIL_COND(!loaded);
-#ifdef DEBUG_ENABLED
- if (pdb_data.size()) {
- mono_debug_close_image(image);
- pdb_data.clear();
- }
-#endif
-
for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) {
memdelete(E->value());
}
@@ -353,8 +370,6 @@ void GDMonoAssembly::unload() {
cached_classes.clear();
cached_raw.clear();
- mono_image_close(image);
-
assembly = NULL;
image = NULL;
loaded = false;
@@ -469,11 +484,12 @@ GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_
GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
if (loaded_asm)
return *loaded_asm;
-
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!FileAccess::exists(p_path));
+#endif
no_search = true;
GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly);
no_search = false;
-
return res;
}
diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h
index 0ba11ac412..39749dfc1d 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.h
+++ b/modules/mono/mono_gd/gd_mono_assembly.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -84,10 +84,6 @@ class GDMonoAssembly {
bool gdobject_class_cache_updated;
Map<StringName, GDMonoClass *> gdobject_class_cache;
-#ifdef DEBUG_ENABLED
- Vector<uint8_t> pdb_data;
-#endif
-
static bool no_search;
static bool in_preload;
static Vector<String> search_dirs;
@@ -102,6 +98,7 @@ class GDMonoAssembly {
static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly);
static GDMonoAssembly *_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly);
+ static GDMonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
static void _wrap_mono_assembly(MonoAssembly *assembly);
friend class GDMono;
@@ -125,6 +122,8 @@ 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(), const String &p_custom_bcl_dir = String());
+
static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
GDMonoAssembly(const String &p_name, const String &p_path = String());
diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp
index 4e515cde28..89a88fcfb2 100644
--- a/modules/mono/mono_gd/gd_mono_class.cpp
+++ b/modules/mono/mono_gd/gd_mono_class.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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);
}
@@ -55,26 +55,32 @@ String GDMonoClass::get_full_name() const {
}
MonoType *GDMonoClass::get_mono_type() {
- // Care, you cannot compare MonoType pointers
+ // Careful, you cannot compare two MonoType*.
+ // There is mono_metadata_type_equal, how is this different from comparing two MonoClass*?
return get_mono_type(mono_class);
}
-bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
+uint32_t GDMonoClass::get_flags() const {
+ return mono_class_get_flags(mono_class);
+}
+
+bool GDMonoClass::is_static() const {
+ uint32_t static_class_flags = MONO_TYPE_ATTR_ABSTRACT | MONO_TYPE_ATTR_SEALED;
+ return (get_flags() & static_class_flags) == static_class_flags;
+}
+bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
return mono_class_is_assignable_from(mono_class, p_from->mono_class);
}
GDMonoClass *GDMonoClass::get_parent_class() {
+ 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
@@ -151,6 +157,7 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) {
StringName name = mono_method_get_name(raw_method);
+ // get_method implicitly fetches methods and adds them to this->methods
GDMonoMethod *method = get_method(raw_method, name);
ERR_CONTINUE(!method);
@@ -158,8 +165,8 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
#ifdef DEBUG_ENABLED
String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")";
- WARN_PRINTS("Method `" + fullname + "` is hidden by Godot API method. Should be `" +
- method->get_full_name_no_class() + "`. In class `" + namespace_name + "." + class_name + "`.");
+ WARN_PRINTS("Method '" + fullname + "' is hidden by Godot API method. Should be '" +
+ method->get_full_name_no_class() + "'. In class '" + namespace_name + "." + class_name + "'.");
#endif
continue;
}
@@ -177,8 +184,8 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
if (m && m->get_name() != name) {
// found
String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")";
- WARN_PRINTS("Method `" + fullname + "` should be `" + m->get_full_name_no_class() +
- "`. In class `" + namespace_name + "." + class_name + "`.");
+ WARN_PRINTS("Method '" + fullname + "' should be '" + m->get_full_name_no_class() +
+ "'. In class '" + namespace_name + "." + class_name + "'.");
break;
}
@@ -251,6 +258,11 @@ bool GDMonoClass::has_fetched_method_unknown_params(const StringName &p_name) {
return get_fetched_method_unknown_params(p_name) != NULL;
}
+bool GDMonoClass::implements_interface(GDMonoClass *p_interface) {
+
+ return mono_class_implements_interface(mono_class, p_interface->get_mono_ptr());
+}
+
GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_count) {
MethodKey key = MethodKey(p_name, p_params_count);
@@ -449,6 +461,21 @@ const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() {
return delegates_list;
}
+const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() {
+
+ if (!method_list_fetched) {
+ void *iter = NULL;
+ MonoMethod *raw_method = NULL;
+ while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) {
+ method_list.push_back(memnew(GDMonoMethod(mono_method_get_name(raw_method), raw_method)));
+ }
+
+ method_list_fetched = true;
+ }
+
+ return method_list;
+}
+
GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly) {
namespace_name = p_namespace;
@@ -460,6 +487,7 @@ GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name
attributes = NULL;
methods_fetched = false;
+ method_list_fetched = false;
fields_fetched = false;
properties_fetched = false;
delegates_fetched = false;
@@ -512,4 +540,8 @@ GDMonoClass::~GDMonoClass() {
methods.clear();
}
+
+ for (int i = 0; i < method_list.size(); ++i) {
+ memdelete(method_list[i]);
+ }
}
diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h
index 477305d503..40e1574927 100644
--- a/modules/mono/mono_gd/gd_mono_class.h
+++ b/modules/mono/mono_gd/gd_mono_class.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -79,9 +79,14 @@ class GDMonoClass {
bool attrs_fetched;
MonoCustomAttrInfo *attributes;
+ // This contains both the original method names and remapped method names from the native Godot identifiers to the C# functions.
+ // Most method-related functions refer to this and it's possible this is unintuitive for outside users; this may be a prime location for refactoring or renaming.
bool methods_fetched;
HashMap<MethodKey, GDMonoMethod *, MethodKey::Hasher> methods;
+ bool method_list_fetched;
+ Vector<GDMonoMethod *> method_list;
+
bool fields_fetched;
Map<StringName, GDMonoField *> fields;
Vector<GDMonoField *> fields_list;
@@ -104,6 +109,9 @@ public:
String get_full_name() const;
MonoType *get_mono_type();
+ uint32_t get_flags() const;
+ bool is_static() const;
+
bool is_assignable_from(GDMonoClass *p_from) const;
_FORCE_INLINE_ StringName get_namespace() const { return namespace_name; }
@@ -113,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();
@@ -127,6 +136,8 @@ public:
void fetch_attributes();
void fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base);
+ bool implements_interface(GDMonoClass *p_interface);
+
GDMonoMethod *get_method(const StringName &p_name, int p_params_count = 0);
GDMonoMethod *get_method(MonoMethod *p_raw_method);
GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name);
@@ -143,6 +154,8 @@ public:
const Vector<GDMonoClass *> &get_all_delegates();
+ const Vector<GDMonoMethod *> &get_all_methods();
+
~GDMonoClass();
};
diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp
index d3a673dc1b..7b8e6f89e9 100644
--- a/modules/mono/mono_gd/gd_mono_field.cpp
+++ b/modules/mono/mono_gd/gd_mono_field.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -40,65 +40,71 @@ void GDMonoField::set_value_raw(MonoObject *p_object, void *p_ptr) {
}
void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_value) {
-#define SET_FROM_STRUCT_AND_BREAK(m_type) \
- { \
- const m_type &val = p_value.operator ::m_type(); \
- MARSHALLED_OUT(m_type, val, raw); \
- mono_field_set_value(p_object, mono_field, raw); \
- break; \
+#define SET_FROM_STRUCT(m_type) \
+ { \
+ GDMonoMarshal::M_##m_type from = MARSHALLED_OUT(m_type, p_value.operator ::m_type()); \
+ mono_field_set_value(p_object, mono_field, &from); \
}
-#define SET_FROM_PRIMITIVE(m_type) \
- { \
- m_type val = p_value.operator m_type(); \
- mono_field_set_value(p_object, mono_field, &val); \
- break; \
- }
-
-#define SET_FROM_ARRAY_AND_BREAK(m_type) \
- { \
- MonoArray *managed = GDMonoMarshal::m_type##_to_mono_array(p_value.operator m_type()); \
- mono_field_set_value(p_object, mono_field, &managed); \
- break; \
+#define SET_FROM_ARRAY(m_type) \
+ { \
+ MonoArray *managed = GDMonoMarshal::m_type##_to_mono_array(p_value.operator ::m_type()); \
+ mono_field_set_value(p_object, mono_field, managed); \
}
switch (type.type_encoding) {
case MONO_TYPE_BOOLEAN: {
- SET_FROM_PRIMITIVE(bool);
+ MonoBoolean val = p_value.operator bool();
+ mono_field_set_value(p_object, mono_field, &val);
+ } break;
+
+ case MONO_TYPE_CHAR: {
+ int16_t val = p_value.operator unsigned short();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_I1: {
- SET_FROM_PRIMITIVE(signed char);
+ int8_t val = p_value.operator signed char();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_I2: {
- SET_FROM_PRIMITIVE(signed short);
+ int16_t val = p_value.operator signed short();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_I4: {
- SET_FROM_PRIMITIVE(signed int);
+ int32_t val = p_value.operator signed int();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_I8: {
- SET_FROM_PRIMITIVE(int64_t);
+ int64_t val = p_value.operator int64_t();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_U1: {
- SET_FROM_PRIMITIVE(unsigned char);
+ uint8_t val = p_value.operator unsigned char();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_U2: {
- SET_FROM_PRIMITIVE(unsigned short);
+ uint16_t val = p_value.operator unsigned short();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_U4: {
- SET_FROM_PRIMITIVE(unsigned int);
+ uint32_t val = p_value.operator unsigned int();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_U8: {
- SET_FROM_PRIMITIVE(uint64_t);
+ uint64_t val = p_value.operator uint64_t();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_R4: {
- SET_FROM_PRIMITIVE(float);
+ float val = p_value.operator float();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_R8: {
- SET_FROM_PRIMITIVE(double);
+ double val = p_value.operator double();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case MONO_TYPE_STRING: {
@@ -109,73 +115,165 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
case MONO_TYPE_VALUETYPE: {
GDMonoClass *tclass = type.type_class;
- if (tclass == CACHED_CLASS(Vector2))
- SET_FROM_STRUCT_AND_BREAK(Vector2);
+ if (tclass == CACHED_CLASS(Vector2)) {
+ SET_FROM_STRUCT(Vector2);
+ break;
+ }
+
+ if (tclass == CACHED_CLASS(Rect2)) {
+ SET_FROM_STRUCT(Rect2);
+ break;
+ }
- if (tclass == CACHED_CLASS(Rect2))
- SET_FROM_STRUCT_AND_BREAK(Rect2);
+ if (tclass == CACHED_CLASS(Transform2D)) {
+ SET_FROM_STRUCT(Transform2D);
+ break;
+ }
- if (tclass == CACHED_CLASS(Transform2D))
- SET_FROM_STRUCT_AND_BREAK(Transform2D);
+ if (tclass == CACHED_CLASS(Vector3)) {
+ SET_FROM_STRUCT(Vector3);
+ break;
+ }
- if (tclass == CACHED_CLASS(Vector3))
- SET_FROM_STRUCT_AND_BREAK(Vector3);
+ if (tclass == CACHED_CLASS(Basis)) {
+ SET_FROM_STRUCT(Basis);
+ break;
+ }
- if (tclass == CACHED_CLASS(Basis))
- SET_FROM_STRUCT_AND_BREAK(Basis);
+ if (tclass == CACHED_CLASS(Quat)) {
+ SET_FROM_STRUCT(Quat);
+ break;
+ }
- if (tclass == CACHED_CLASS(Quat))
- SET_FROM_STRUCT_AND_BREAK(Quat);
+ if (tclass == CACHED_CLASS(Transform)) {
+ SET_FROM_STRUCT(Transform);
+ break;
+ }
- if (tclass == CACHED_CLASS(Transform))
- SET_FROM_STRUCT_AND_BREAK(Transform);
+ if (tclass == CACHED_CLASS(AABB)) {
+ SET_FROM_STRUCT(AABB);
+ break;
+ }
- if (tclass == CACHED_CLASS(AABB))
- SET_FROM_STRUCT_AND_BREAK(AABB);
+ if (tclass == CACHED_CLASS(Color)) {
+ SET_FROM_STRUCT(Color);
+ break;
+ }
- if (tclass == CACHED_CLASS(Color))
- SET_FROM_STRUCT_AND_BREAK(Color);
+ if (tclass == CACHED_CLASS(Plane)) {
+ SET_FROM_STRUCT(Plane);
+ break;
+ }
- if (tclass == CACHED_CLASS(Plane))
- SET_FROM_STRUCT_AND_BREAK(Plane);
+ if (mono_class_is_enum(tclass->get_mono_ptr())) {
+ MonoType *enum_basetype = mono_class_enum_basetype(tclass->get_mono_ptr());
+ switch (mono_type_get_type(enum_basetype)) {
+ case MONO_TYPE_BOOLEAN: {
+ MonoBoolean val = p_value.operator bool();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_CHAR: {
+ uint16_t val = p_value.operator unsigned short();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_I1: {
+ int8_t val = p_value.operator signed char();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_I2: {
+ int16_t val = p_value.operator signed short();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_I4: {
+ int32_t val = p_value.operator signed int();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_I8: {
+ int64_t val = p_value.operator int64_t();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_U1: {
+ uint8_t val = p_value.operator unsigned char();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_U2: {
+ uint16_t val = p_value.operator unsigned short();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_U4: {
+ uint32_t val = p_value.operator unsigned int();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ case MONO_TYPE_U8: {
+ uint64_t val = p_value.operator uint64_t();
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+ default: {
+ ERR_FAIL_MSG("Attempted to convert Variant to a managed enum value of unmarshallable base type.");
+ }
+ }
- if (mono_class_is_enum(tclass->get_mono_ptr()))
- SET_FROM_PRIMITIVE(signed int);
+ break;
+ }
- ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + tclass->get_name());
- ERR_FAIL();
+ ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + tclass->get_name() + "'.");
} break;
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type());
- if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
- SET_FROM_ARRAY_AND_BREAK(Array);
+ if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) {
+ SET_FROM_ARRAY(Array);
+ break;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(uint8_t))
- SET_FROM_ARRAY_AND_BREAK(PoolByteArray);
+ if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) {
+ SET_FROM_ARRAY(PoolByteArray);
+ break;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int32_t))
- SET_FROM_ARRAY_AND_BREAK(PoolIntArray);
+ if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
+ SET_FROM_ARRAY(PoolIntArray);
+ break;
+ }
- if (array_type->eklass == REAL_T_MONOCLASS)
- SET_FROM_ARRAY_AND_BREAK(PoolRealArray);
+ if (array_type->eklass == REAL_T_MONOCLASS) {
+ SET_FROM_ARRAY(PoolRealArray);
+ break;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(String))
- SET_FROM_ARRAY_AND_BREAK(PoolStringArray);
+ if (array_type->eklass == CACHED_CLASS_RAW(String)) {
+ SET_FROM_ARRAY(PoolStringArray);
+ break;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2))
- SET_FROM_ARRAY_AND_BREAK(PoolVector2Array);
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
+ SET_FROM_ARRAY(PoolVector2Array);
+ break;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3))
- SET_FROM_ARRAY_AND_BREAK(PoolVector3Array);
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
+ SET_FROM_ARRAY(PoolVector3Array);
+ break;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Color))
- SET_FROM_ARRAY_AND_BREAK(PoolColorArray);
+ if (array_type->eklass == CACHED_CLASS_RAW(Color)) {
+ SET_FROM_ARRAY(PoolColorArray);
+ break;
+ }
- ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type.");
- ERR_FAIL();
+ ERR_FAIL_MSG("Attempted to convert Variant to a managed array of unmarshallable element type.");
} break;
case MONO_TYPE_CLASS: {
@@ -212,40 +310,101 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
break;
}
- ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name());
- ERR_FAIL();
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
+
+ MonoReflectionType *key_reftype, *value_reftype;
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(),
+ GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+
+ if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(),
+ GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
+ 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_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + type_class->get_name() + "'.");
} break;
case MONO_TYPE_OBJECT: {
// Variant
switch (p_value.get_type()) {
case Variant::BOOL: {
- SET_FROM_PRIMITIVE(bool);
+ MonoBoolean val = p_value.operator bool();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case Variant::INT: {
- SET_FROM_PRIMITIVE(int);
+ int32_t val = p_value.operator signed int();
+ mono_field_set_value(p_object, mono_field, &val);
} break;
case Variant::REAL: {
#ifdef REAL_T_IS_DOUBLE
- SET_FROM_PRIMITIVE(double);
+ double val = p_value.operator double();
+ mono_field_set_value(p_object, mono_field, &val);
#else
- SET_FROM_PRIMITIVE(float);
+ float val = p_value.operator float();
+ mono_field_set_value(p_object, mono_field, &val);
#endif
} break;
case Variant::STRING: {
MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value);
mono_field_set_value(p_object, mono_field, mono_string);
} break;
- case Variant::VECTOR2: SET_FROM_STRUCT_AND_BREAK(Vector2);
- case Variant::RECT2: SET_FROM_STRUCT_AND_BREAK(Rect2);
- case Variant::VECTOR3: SET_FROM_STRUCT_AND_BREAK(Vector3);
- case Variant::TRANSFORM2D: SET_FROM_STRUCT_AND_BREAK(Transform2D);
- case Variant::PLANE: SET_FROM_STRUCT_AND_BREAK(Plane);
- case Variant::QUAT: SET_FROM_STRUCT_AND_BREAK(Quat);
- case Variant::AABB: SET_FROM_STRUCT_AND_BREAK(AABB);
- case Variant::BASIS: SET_FROM_STRUCT_AND_BREAK(Basis);
- case Variant::TRANSFORM: SET_FROM_STRUCT_AND_BREAK(Transform);
- case Variant::COLOR: SET_FROM_STRUCT_AND_BREAK(Color);
+ case Variant::VECTOR2: {
+ SET_FROM_STRUCT(Vector2);
+ } break;
+ case Variant::RECT2: {
+ SET_FROM_STRUCT(Rect2);
+ } break;
+ case Variant::VECTOR3: {
+ SET_FROM_STRUCT(Vector3);
+ } break;
+ case Variant::TRANSFORM2D: {
+ SET_FROM_STRUCT(Transform2D);
+ } break;
+ case Variant::PLANE: {
+ SET_FROM_STRUCT(Plane);
+ } break;
+ case Variant::QUAT: {
+ SET_FROM_STRUCT(Quat);
+ } break;
+ case Variant::AABB: {
+ SET_FROM_STRUCT(AABB);
+ } break;
+ case Variant::BASIS: {
+ SET_FROM_STRUCT(Basis);
+ } break;
+ case Variant::TRANSFORM: {
+ SET_FROM_STRUCT(Transform);
+ } break;
+ case Variant::COLOR: {
+ SET_FROM_STRUCT(Color);
+ } break;
case Variant::NODE_PATH: {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath());
mono_field_set_value(p_object, mono_field, managed);
@@ -267,53 +426,90 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_BYTE_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolByteArray);
- case Variant::POOL_INT_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolIntArray);
- case Variant::POOL_REAL_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolRealArray);
- case Variant::POOL_STRING_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolStringArray);
- case Variant::POOL_VECTOR2_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolVector2Array);
- case Variant::POOL_VECTOR3_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolVector3Array);
- case Variant::POOL_COLOR_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolColorArray);
-#undef SET_FROM_ARRAY_AND_BREAK
+ case Variant::POOL_BYTE_ARRAY: {
+ SET_FROM_ARRAY(PoolByteArray);
+ } break;
+ case Variant::POOL_INT_ARRAY: {
+ SET_FROM_ARRAY(PoolIntArray);
+ } break;
+ case Variant::POOL_REAL_ARRAY: {
+ SET_FROM_ARRAY(PoolRealArray);
+ } break;
+ case Variant::POOL_STRING_ARRAY: {
+ SET_FROM_ARRAY(PoolStringArray);
+ } break;
+ case Variant::POOL_VECTOR2_ARRAY: {
+ SET_FROM_ARRAY(PoolVector2Array);
+ } break;
+ case Variant::POOL_VECTOR3_ARRAY: {
+ SET_FROM_ARRAY(PoolVector3Array);
+ } break;
+ case Variant::POOL_COLOR_ARRAY: {
+ SET_FROM_ARRAY(PoolColorArray);
+ } break;
default: break;
}
} break;
case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
-
- MonoException *exc = NULL;
-
- GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
- MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
- 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;
+ }
+
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
- GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
- MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ 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 (is_array) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class);
+ if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
mono_field_set_value(p_object, mono_field, managed);
break;
}
+
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(),
+ GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+
+ if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
+ if (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: {
- ERR_PRINTS(String() + "Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding));
+ ERR_PRINTS("Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding) + ".");
} break;
}
+#undef SET_FROM_ARRAY_AND_BREAK
#undef SET_FROM_STRUCT_AND_BREAK
-#undef SET_FROM_PRIMITIVE
}
MonoObject *GDMonoField::get_value(MonoObject *p_object) {
@@ -367,20 +563,20 @@ bool GDMonoField::is_static() {
return mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_STATIC;
}
-GDMonoClassMember::Visibility GDMonoField::get_visibility() {
+IMonoClassMember::Visibility GDMonoField::get_visibility() {
switch (mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_FIELD_ACCESS_MASK) {
case MONO_FIELD_ATTR_PRIVATE:
- return GDMonoClassMember::PRIVATE;
+ return IMonoClassMember::PRIVATE;
case MONO_FIELD_ATTR_FAM_AND_ASSEM:
- return GDMonoClassMember::PROTECTED_AND_INTERNAL;
+ return IMonoClassMember::PROTECTED_AND_INTERNAL;
case MONO_FIELD_ATTR_ASSEMBLY:
- return GDMonoClassMember::INTERNAL;
+ return IMonoClassMember::INTERNAL;
case MONO_FIELD_ATTR_FAMILY:
- return GDMonoClassMember::PROTECTED;
+ return IMonoClassMember::PROTECTED;
case MONO_FIELD_ATTR_PUBLIC:
- return GDMonoClassMember::PUBLIC;
+ return IMonoClassMember::PUBLIC;
default:
- ERR_FAIL_V(GDMonoClassMember::PRIVATE);
+ ERR_FAIL_V(IMonoClassMember::PRIVATE);
}
}
diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h
index a6b368c4d6..a7727ddf34 100644
--- a/modules/mono/mono_gd/gd_mono_field.h
+++ b/modules/mono/mono_gd/gd_mono_field.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -32,10 +32,10 @@
#define GDMONOFIELD_H
#include "gd_mono.h"
-#include "gd_mono_class_member.h"
#include "gd_mono_header.h"
+#include "i_mono_class_member.h"
-class GDMonoField : public GDMonoClassMember {
+class GDMonoField : public IMonoClassMember {
GDMonoClass *owner;
MonoClassField *mono_field;
@@ -47,15 +47,17 @@ class GDMonoField : public GDMonoClassMember {
MonoCustomAttrInfo *attributes;
public:
- virtual MemberType get_member_type() { return MEMBER_TYPE_FIELD; }
+ virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; }
- virtual StringName get_name() { return name; }
+ virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_FIELD; }
- virtual bool is_static();
- virtual Visibility get_visibility();
+ virtual StringName get_name() const GD_FINAL { return name; }
- virtual bool has_attribute(GDMonoClass *p_attr_class);
- virtual MonoObject *get_attribute(GDMonoClass *p_attr_class);
+ virtual bool is_static() GD_FINAL;
+ virtual Visibility get_visibility() GD_FINAL;
+
+ virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL;
+ virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL;
void fetch_attributes();
_FORCE_INLINE_ ManagedType get_type() const { return type; }
diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/mono_gd/gd_mono_header.h
index 2fe05006f1..d7962eac8b 100644
--- a/modules/mono/mono_gd/gd_mono_header.h
+++ b/modules/mono/mono_gd/gd_mono_header.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -35,29 +35,12 @@
class GDMonoAssembly;
class GDMonoClass;
-class GDMonoClassMember;
class GDMonoField;
-class GDMonoProperty;
class GDMonoMethod;
+class GDMonoProperty;
-struct ManagedType {
- int type_encoding;
- GDMonoClass *type_class;
-
- ManagedType() {
- type_encoding = 0;
- type_class = NULL;
- }
-};
-
-typedef union {
- uint32_t _uint32;
- float _float;
-} mono_float;
+class IMonoClassMember;
-typedef union {
- uint64_t _uint64;
- float _double;
-} mono_double;
+#include "managed_type.h"
#endif // GD_MONO_HEADER_H
diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp
index 505c030ca1..3324ecb3a8 100644
--- a/modules/mono/mono_gd/gd_mono_internals.cpp
+++ b/modules/mono/mono_gd/gd_mono_internals.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -34,6 +34,8 @@
#include "../mono_gc_handle.h"
#include "../utils/macros.h"
#include "../utils/thread_local.h"
+#include "gd_mono_class.h"
+#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"
#include <mono/metadata/exception.h>
@@ -46,7 +48,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
CRASH_COND(!unmanaged);
- // All mono objects created from the managed world (e.g.: `new Player()`)
+ // All mono objects created from the managed world (e.g.: 'new Player()')
// need to have a CSharpScript in order for their methods to be callable from the unmanaged side
Reference *ref = Object::cast_to<Reference>(unmanaged);
@@ -55,24 +57,69 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
CRASH_COND(!klass);
- Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) :
- MonoGCHandle::create_strong(managed);
+ GDMonoClass *native = GDMonoUtils::get_class_native_base(klass);
- Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass);
+ CRASH_COND(native == NULL);
+
+ if (native == klass) {
+ // If it's just a wrapper Godot class and not a custom inheriting class, then attach a
+ // script binding instead. One of the advantages of this is that if a script is attached
+ // later and it's not a C# script, then the managed object won't have to be disposed.
+ // Another reason for doing this is that this instance could outlive CSharpLanguage, which would
+ // be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621
+
+ CSharpScriptBinding script_binding;
+
+ script_binding.inited = true;
+ script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass);
+ script_binding.wrapper_class = klass;
+ script_binding.gchandle = MonoGCHandle::create_strong(managed);
+ script_binding.owner = unmanaged;
+
+ 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)
+ ref->reference();
+ }
+
+ // The object was just created, no script instance binding should have been attached
+ CRASH_COND(unmanaged->has_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()));
+
+ void *data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(unmanaged, script_binding);
+
+ // Should be thread safe because the object was just created and nothing else should be referencing it
+ unmanaged->set_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index(), data);
+
+ return;
+ }
+
+ Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed);
+
+ Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass, native);
CRASH_COND(script.is_null());
ScriptInstance *si = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle);
unmanaged->set_script_and_instance(script.get_ref_ptr(), si);
-
- return;
}
void unhandled_exception(MonoException *p_exc) {
mono_unhandled_exception((MonoObject *)p_exc); // prints the exception as well
- abort();
- _UNREACHABLE_();
+
+ if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) {
+ // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders
+ GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL);
+ GD_UNREACHABLE();
+ } else {
+#ifdef DEBUG_ENABLED
+ GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc);
+ if (ScriptDebugger::get_singleton())
+ ScriptDebugger::get_singleton()->idle_poll();
+#endif
+ }
}
} // namespace GDMonoInternals
diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h
index 50cadcedf6..0d82723913 100644
--- a/modules/mono/mono_gd/gd_mono_internals.h
+++ b/modules/mono/mono_gd/gd_mono_internals.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -45,7 +45,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged);
* Do not call this function directly.
* Use GDMonoUtils::debug_unhandled_exception(MonoException *) instead.
*/
-_NO_RETURN_ void unhandled_exception(MonoException *p_exc);
+void unhandled_exception(MonoException *p_exc);
} // namespace GDMonoInternals
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index b7bb2cb2d6..7b3421fdb3 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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) {
@@ -52,7 +53,7 @@ static int log_level_get_id(const char *p_log_level) {
return -1;
}
-void gdmono_MonoLogCallback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) {
+static void mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) {
FileAccess *f = GDMonoLog::get_singleton()->get_log_file();
@@ -71,9 +72,12 @@ void gdmono_MonoLogCallback(const char *log_domain, const char *log_level, const
}
if (fatal) {
- ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: " + GDMonoLog::get_singleton()->get_log_file_path() + "\n");
- // If we were to abort without flushing, the log wouldn't get written.
+ ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: '" + GDMonoLog::get_singleton()->get_log_file_path() + "'.");
+ // Make sure to flush before aborting
f->flush();
+ f->close();
+ memdelete(f);
+
abort();
}
}
@@ -86,30 +90,21 @@ bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) {
DirAccessRef diraccess = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!diraccess, false);
Error logs_mkdir_err = diraccess->make_dir_recursive(p_logs_dir);
- ERR_EXPLAIN("Failed to create mono logs directory");
- ERR_FAIL_COND_V(logs_mkdir_err != OK, false);
+ ERR_FAIL_COND_V_MSG(logs_mkdir_err != OK, false, "Failed to create mono logs directory.");
}
return true;
}
-void GDMonoLog::_open_log_file(const String &p_file_path) {
-
- log_file = FileAccess::open(p_file_path, FileAccess::WRITE);
-
- ERR_EXPLAIN("Failed to create log file");
- ERR_FAIL_COND(!log_file);
-}
-
void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
- static const uint64_t MAX_SECS = 5 * 86400;
+ static const uint64_t MAX_SECS = 5 * 86400; // 5 days
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND(!da);
Error err = da->change_dir(p_logs_dir);
- ERR_FAIL_COND(err != OK);
+ ERR_FAIL_COND_MSG(err != OK, "Cannot change directory to '" + p_logs_dir + "'.");
ERR_FAIL_COND(da->list_dir_begin() != OK);
@@ -120,10 +115,9 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
if (!current.ends_with(".txt"))
continue;
- String name = current.get_basename();
- uint64_t unixtime = (uint64_t)name.to_int64();
+ uint64_t modified_time = FileAccess::get_modified_time(da->get_current_dir().plus_file(current));
- if (OS::get_singleton()->get_unix_time() - unixtime > MAX_SECS) {
+ if (OS::get_singleton()->get_unix_time() - modified_time > MAX_SECS) {
da->remove(current);
}
}
@@ -133,27 +127,48 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
void GDMonoLog::initialize() {
+ CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8();
+
+ if (log_level.length() != 0 && log_level_get_id(log_level.get_data()) == -1) {
+ ERR_PRINTS(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'.");
+ log_level = CharString();
+ }
+
+ if (log_level.length() == 0) {
#ifdef DEBUG_ENABLED
- const char *log_level = "debug";
+ log_level = String("info").utf8();
#else
- const char *log_level = "warning";
+ log_level = String("warning").utf8();
#endif
+ }
String logs_dir = GodotSharpDirs::get_mono_logs_dir();
if (_try_create_logs_dir(logs_dir)) {
_delete_old_log_files(logs_dir);
- log_file_path = logs_dir.plus_file(String::num_int64(OS::get_singleton()->get_unix_time()) + ".txt");
- _open_log_file(log_file_path);
+ OS::Date date_now = OS::get_singleton()->get_date();
+ OS::Time time_now = OS::get_singleton()->get_time();
+ int pid = OS::get_singleton()->get_process_id();
+
+ 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);
+
+ log_file_path = logs_dir.plus_file(log_file_name);
+
+ log_file = FileAccess::open(log_file_path, FileAccess::WRITE);
+ if (!log_file) {
+ ERR_PRINTS("Mono: Cannot create log file at: " + log_file_path);
+ }
}
- mono_trace_set_level_string(log_level);
- log_level_id = log_level_get_id(log_level);
+ mono_trace_set_level_string(log_level.get_data());
+ log_level_id = log_level_get_id(log_level.get_data());
if (log_file) {
- print_verbose("Mono: Logfile is " + log_file_path);
- mono_trace_set_log_handler(gdmono_MonoLogCallback, this);
+ OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data());
+ mono_trace_set_log_handler(mono_log_callback, this);
} else {
OS::get_singleton()->printerr("Mono: No log file, using default log handler\n");
}
diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h
index 3b4ff07b7b..91d0557aa3 100644
--- a/modules/mono/mono_gd/gd_mono_log.h
+++ b/modules/mono/mono_gd/gd_mono_log.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -41,7 +41,6 @@ class GDMonoLog {
String log_file_path;
bool _try_create_logs_dir(const String &p_logs_dir);
- void _open_log_file(const String &p_file_path);
void _delete_old_log_files(const String &p_logs_dir);
static GDMonoLog *singleton;
diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp
index de91e71bab..7aac691102 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.cpp
+++ b/modules/mono/mono_gd/gd_mono_marshal.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -35,20 +35,6 @@
namespace GDMonoMarshal {
-#define RETURN_BOXED_STRUCT(m_t, m_var_in) \
- { \
- const m_t &m_in = m_var_in->operator ::m_t(); \
- MARSHALLED_OUT(m_t, m_in, raw); \
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(m_t), raw); \
- }
-
-#define RETURN_UNBOXED_STRUCT(m_t, m_var_in) \
- { \
- float *raw = (float *)mono_object_unbox(m_var_in); \
- MARSHALLED_IN(m_t, raw, ret); \
- return ret; \
- }
-
Variant::Type managed_to_variant_type(const ManagedType &p_type) {
switch (p_type.type_encoding) {
case MONO_TYPE_BOOLEAN:
@@ -82,39 +68,39 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
} break;
case MONO_TYPE_VALUETYPE: {
- GDMonoClass *tclass = p_type.type_class;
+ GDMonoClass *vtclass = p_type.type_class;
- if (tclass == CACHED_CLASS(Vector2))
+ if (vtclass == CACHED_CLASS(Vector2))
return Variant::VECTOR2;
- if (tclass == CACHED_CLASS(Rect2))
+ if (vtclass == CACHED_CLASS(Rect2))
return Variant::RECT2;
- if (tclass == CACHED_CLASS(Transform2D))
+ if (vtclass == CACHED_CLASS(Transform2D))
return Variant::TRANSFORM2D;
- if (tclass == CACHED_CLASS(Vector3))
+ if (vtclass == CACHED_CLASS(Vector3))
return Variant::VECTOR3;
- if (tclass == CACHED_CLASS(Basis))
+ if (vtclass == CACHED_CLASS(Basis))
return Variant::BASIS;
- if (tclass == CACHED_CLASS(Quat))
+ if (vtclass == CACHED_CLASS(Quat))
return Variant::QUAT;
- if (tclass == CACHED_CLASS(Transform))
+ if (vtclass == CACHED_CLASS(Transform))
return Variant::TRANSFORM;
- if (tclass == CACHED_CLASS(AABB))
+ if (vtclass == CACHED_CLASS(AABB))
return Variant::AABB;
- if (tclass == CACHED_CLASS(Color))
+ if (vtclass == CACHED_CLASS(Color))
return Variant::COLOR;
- if (tclass == CACHED_CLASS(Plane))
+ if (vtclass == CACHED_CLASS(Plane))
return Variant::PLANE;
- if (mono_class_is_enum(tclass->get_mono_ptr()))
+ if (mono_class_is_enum(vtclass->get_mono_ptr()))
return Variant::INT;
} break;
@@ -170,26 +156,52 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
if (CACHED_CLASS(Array) == type_class) {
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());
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
- MonoException *exc = NULL;
- GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
- MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
+ return Variant::DICTIONARY;
+ }
- if (is_dict) {
+ 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;
}
- exc = NULL;
- GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
- MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype))
+ return Variant::ARRAY;
- if (is_array) {
+ if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
return Variant::ARRAY;
}
} break;
@@ -202,12 +214,72 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
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);
- ERR_EXPLAIN("Conversion of MonoString to UTF8 failed.");
- ERR_FAIL_COND_V(!mono_error_ok(&error), String());
+ if (!mono_error_ok(&error)) {
+ ERR_PRINTS(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&error) + "'.");
+ mono_error_cleanup(&error);
+ return String();
+ }
String ret = String::utf8(utf8);
@@ -227,7 +299,7 @@ String mono_to_utf16_string(MonoString *p_mono_string) {
ret.set(len, 0);
CharType *src = (CharType *)mono_string_chars(p_mono_string);
- CharType *dst = &(ret.operator[](0));
+ CharType *dst = ret.ptrw();
for (int i = 0; i < len; i++) {
dst[i] = src[i];
@@ -252,16 +324,21 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
return BOX_BOOLEAN(val);
}
+ case MONO_TYPE_CHAR: {
+ uint16_t val = p_var->operator unsigned short();
+ return BOX_UINT16(val);
+ }
+
case MONO_TYPE_I1: {
- char val = p_var->operator signed char();
+ int8_t val = p_var->operator signed char();
return BOX_INT8(val);
}
case MONO_TYPE_I2: {
- short val = p_var->operator signed short();
+ int16_t val = p_var->operator signed short();
return BOX_INT16(val);
}
case MONO_TYPE_I4: {
- int val = p_var->operator signed int();
+ int32_t val = p_var->operator signed int();
return BOX_INT32(val);
}
case MONO_TYPE_I8: {
@@ -270,15 +347,15 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
case MONO_TYPE_U1: {
- char val = p_var->operator unsigned char();
+ uint8_t val = p_var->operator unsigned char();
return BOX_UINT8(val);
}
case MONO_TYPE_U2: {
- short val = p_var->operator unsigned short();
+ uint16_t val = p_var->operator unsigned short();
return BOX_UINT16(val);
}
case MONO_TYPE_U4: {
- int val = p_var->operator unsigned int();
+ uint32_t val = p_var->operator unsigned int();
return BOX_UINT32(val);
}
case MONO_TYPE_U8: {
@@ -300,41 +377,106 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
} break;
case MONO_TYPE_VALUETYPE: {
- GDMonoClass *tclass = p_type.type_class;
+ GDMonoClass *vtclass = p_type.type_class;
- if (tclass == CACHED_CLASS(Vector2))
- RETURN_BOXED_STRUCT(Vector2, p_var);
+ if (vtclass == CACHED_CLASS(Vector2)) {
+ GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var->operator ::Vector2());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from);
+ }
- if (tclass == CACHED_CLASS(Rect2))
- RETURN_BOXED_STRUCT(Rect2, p_var);
+ if (vtclass == CACHED_CLASS(Rect2)) {
+ GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from);
+ }
- if (tclass == CACHED_CLASS(Transform2D))
- RETURN_BOXED_STRUCT(Transform2D, p_var);
+ if (vtclass == CACHED_CLASS(Transform2D)) {
+ GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from);
+ }
- if (tclass == CACHED_CLASS(Vector3))
- RETURN_BOXED_STRUCT(Vector3, p_var);
+ if (vtclass == CACHED_CLASS(Vector3)) {
+ GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var->operator ::Vector3());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from);
+ }
- if (tclass == CACHED_CLASS(Basis))
- RETURN_BOXED_STRUCT(Basis, p_var);
+ if (vtclass == CACHED_CLASS(Basis)) {
+ GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var->operator ::Basis());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from);
+ }
- if (tclass == CACHED_CLASS(Quat))
- RETURN_BOXED_STRUCT(Quat, p_var);
+ if (vtclass == CACHED_CLASS(Quat)) {
+ GDMonoMarshal::M_Quat from = MARSHALLED_OUT(Quat, p_var->operator ::Quat());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quat), &from);
+ }
- if (tclass == CACHED_CLASS(Transform))
- RETURN_BOXED_STRUCT(Transform, p_var);
+ if (vtclass == CACHED_CLASS(Transform)) {
+ GDMonoMarshal::M_Transform from = MARSHALLED_OUT(Transform, p_var->operator ::Transform());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform), &from);
+ }
- if (tclass == CACHED_CLASS(AABB))
- RETURN_BOXED_STRUCT(AABB, p_var);
+ if (vtclass == CACHED_CLASS(AABB)) {
+ GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var->operator ::AABB());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from);
+ }
- if (tclass == CACHED_CLASS(Color))
- RETURN_BOXED_STRUCT(Color, p_var);
+ if (vtclass == CACHED_CLASS(Color)) {
+ GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var->operator ::Color());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from);
+ }
- if (tclass == CACHED_CLASS(Plane))
- RETURN_BOXED_STRUCT(Plane, p_var);
+ if (vtclass == CACHED_CLASS(Plane)) {
+ GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var->operator ::Plane());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from);
+ }
- if (mono_class_is_enum(tclass->get_mono_ptr())) {
- int val = p_var->operator signed int();
- return BOX_ENUM(tclass->get_mono_ptr(), val);
+ if (mono_class_is_enum(vtclass->get_mono_ptr())) {
+ MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr());
+ MonoClass *enum_baseclass = mono_class_from_mono_type(enum_basetype);
+ switch (mono_type_get_type(enum_basetype)) {
+ case MONO_TYPE_BOOLEAN: {
+ MonoBoolean val = p_var->operator bool();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_CHAR: {
+ uint16_t val = p_var->operator unsigned short();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_I1: {
+ int8_t val = p_var->operator signed char();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_I2: {
+ int16_t val = p_var->operator signed short();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_I4: {
+ int32_t val = p_var->operator signed int();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_I8: {
+ int64_t val = p_var->operator int64_t();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_U1: {
+ uint8_t val = p_var->operator unsigned char();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_U2: {
+ uint16_t val = p_var->operator unsigned short();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_U4: {
+ uint32_t val = p_var->operator unsigned int();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ case MONO_TYPE_U8: {
+ uint64_t val = p_var->operator uint64_t();
+ return BOX_ENUM(enum_baseclass, val);
+ }
+ default: {
+ ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed enum value of unmarshallable base type.");
+ }
+ }
}
} break;
@@ -366,8 +508,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
if (array_type->eklass == CACHED_CLASS_RAW(Color))
return (MonoObject *)PoolColorArray_to_mono_array(p_var->operator PoolColorArray());
- ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type.");
- ERR_FAIL_V(NULL);
+ ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed array of unmarshallable element type.");
} break;
case MONO_TYPE_CLASS: {
@@ -393,6 +534,34 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
if (CACHED_CLASS(Array) == type_class) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
}
+
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
+
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
+
+ MonoReflectionType *key_reftype, *value_reftype;
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
+ return GDMonoUtils::create_managed_from(p_var->operator Dictionary(),
+ GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
+ }
+
+ if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
+ return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
+ }
+
+ MonoReflectionType *elem_reftype;
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
+ return GDMonoUtils::create_managed_from(p_var->operator Array(),
+ GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
+ }
+
+ if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
+ if (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: {
// Variant
@@ -402,7 +571,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
return BOX_BOOLEAN(val);
}
case Variant::INT: {
- int val = p_var->operator signed int();
+ int32_t val = p_var->operator signed int();
return BOX_INT32(val);
}
case Variant::REAL: {
@@ -416,33 +585,52 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
case Variant::STRING:
return (MonoObject *)mono_string_from_godot(p_var->operator String());
- case Variant::VECTOR2:
- RETURN_BOXED_STRUCT(Vector2, p_var);
- case Variant::RECT2:
- RETURN_BOXED_STRUCT(Rect2, p_var);
- case Variant::VECTOR3:
- RETURN_BOXED_STRUCT(Vector3, p_var);
- case Variant::TRANSFORM2D:
- RETURN_BOXED_STRUCT(Transform2D, p_var);
- case Variant::PLANE:
- RETURN_BOXED_STRUCT(Plane, p_var);
- case Variant::QUAT:
- RETURN_BOXED_STRUCT(Quat, p_var);
- case Variant::AABB:
- RETURN_BOXED_STRUCT(AABB, p_var);
- case Variant::BASIS:
- RETURN_BOXED_STRUCT(Basis, p_var);
- case Variant::TRANSFORM:
- RETURN_BOXED_STRUCT(Transform, p_var);
- case Variant::COLOR:
- RETURN_BOXED_STRUCT(Color, p_var);
+ case Variant::VECTOR2: {
+ GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var->operator ::Vector2());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from);
+ }
+ case Variant::RECT2: {
+ GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from);
+ }
+ case Variant::VECTOR3: {
+ GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var->operator ::Vector3());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from);
+ }
+ case Variant::TRANSFORM2D: {
+ GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from);
+ }
+ case Variant::PLANE: {
+ GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var->operator ::Plane());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from);
+ }
+ case Variant::QUAT: {
+ GDMonoMarshal::M_Quat from = MARSHALLED_OUT(Quat, p_var->operator ::Quat());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quat), &from);
+ }
+ case Variant::AABB: {
+ GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var->operator ::AABB());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from);
+ }
+ case Variant::BASIS: {
+ GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var->operator ::Basis());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from);
+ }
+ case Variant::TRANSFORM: {
+ GDMonoMarshal::M_Transform from = MARSHALLED_OUT(Transform, p_var->operator ::Transform());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform), &from);
+ }
+ case Variant::COLOR: {
+ GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var->operator ::Color());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from);
+ }
case Variant::NODE_PATH:
return GDMonoUtils::create_managed_from(p_var->operator NodePath());
case Variant::_RID:
return GDMonoUtils::create_managed_from(p_var->operator RID());
- case Variant::OBJECT: {
+ case Variant::OBJECT:
return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *());
- }
case Variant::DICTIONARY:
return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
case Variant::ARRAY:
@@ -466,52 +654,64 @@ 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::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
- MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&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::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
- MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&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))) {
+ 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;
}
- ERR_EXPLAIN(String() + "Attempted to convert Variant to an unmarshallable managed type. Name: \'" +
- p_type.type_class->get_name() + "\' Encoding: " + itos(p_type.type_encoding));
- ERR_FAIL_V(NULL);
+ ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to an unmarshallable managed type. Name: '" +
+ p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + ".");
}
Variant mono_object_to_variant(MonoObject *p_obj) {
if (!p_obj)
return Variant();
- GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_object_get_class(p_obj));
- ERR_FAIL_COND_V(!tclass, Variant());
-
- MonoType *raw_type = tclass->get_mono_type();
+ ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj));
- ManagedType type;
-
- type.type_encoding = mono_type_get_type(raw_type);
- type.type_class = tclass;
+ ERR_FAIL_COND_V(!type.type_class, Variant());
switch (type.type_encoding) {
case MONO_TYPE_BOOLEAN:
return (bool)unbox<MonoBoolean>(p_obj);
+ case MONO_TYPE_CHAR:
+ return unbox<uint16_t>(p_obj);
+
case MONO_TYPE_I1:
return unbox<int8_t>(p_obj);
case MONO_TYPE_I2:
@@ -542,39 +742,39 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
} break;
case MONO_TYPE_VALUETYPE: {
- GDMonoClass *tclass = type.type_class;
+ GDMonoClass *vtclass = type.type_class;
- if (tclass == CACHED_CLASS(Vector2))
- RETURN_UNBOXED_STRUCT(Vector2, p_obj);
+ if (vtclass == CACHED_CLASS(Vector2))
+ return MARSHALLED_IN(Vector2, (GDMonoMarshal::M_Vector2 *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Rect2))
- RETURN_UNBOXED_STRUCT(Rect2, p_obj);
+ if (vtclass == CACHED_CLASS(Rect2))
+ return MARSHALLED_IN(Rect2, (GDMonoMarshal::M_Rect2 *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Transform2D))
- RETURN_UNBOXED_STRUCT(Transform2D, p_obj);
+ if (vtclass == CACHED_CLASS(Transform2D))
+ return MARSHALLED_IN(Transform2D, (GDMonoMarshal::M_Transform2D *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Vector3))
- RETURN_UNBOXED_STRUCT(Vector3, p_obj);
+ if (vtclass == CACHED_CLASS(Vector3))
+ return MARSHALLED_IN(Vector3, (GDMonoMarshal::M_Vector3 *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Basis))
- RETURN_UNBOXED_STRUCT(Basis, p_obj);
+ if (vtclass == CACHED_CLASS(Basis))
+ return MARSHALLED_IN(Basis, (GDMonoMarshal::M_Basis *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Quat))
- RETURN_UNBOXED_STRUCT(Quat, p_obj);
+ if (vtclass == CACHED_CLASS(Quat))
+ return MARSHALLED_IN(Quat, (GDMonoMarshal::M_Quat *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Transform))
- RETURN_UNBOXED_STRUCT(Transform, p_obj);
+ if (vtclass == CACHED_CLASS(Transform))
+ return MARSHALLED_IN(Transform, (GDMonoMarshal::M_Transform *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(AABB))
- RETURN_UNBOXED_STRUCT(AABB, p_obj);
+ if (vtclass == CACHED_CLASS(AABB))
+ return MARSHALLED_IN(AABB, (GDMonoMarshal::M_AABB *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Color))
- RETURN_UNBOXED_STRUCT(Color, p_obj);
+ if (vtclass == CACHED_CLASS(Color))
+ return MARSHALLED_IN(Color, (GDMonoMarshal::M_Color *)mono_object_unbox(p_obj));
- if (tclass == CACHED_CLASS(Plane))
- RETURN_UNBOXED_STRUCT(Plane, p_obj);
+ if (vtclass == CACHED_CLASS(Plane))
+ return MARSHALLED_IN(Plane, (GDMonoMarshal::M_Plane *)mono_object_unbox(p_obj));
- if (mono_class_is_enum(tclass->get_mono_ptr()))
+ if (mono_class_is_enum(vtclass->get_mono_ptr()))
return unbox<int32_t>(p_obj);
} break;
@@ -606,8 +806,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
if (array_type->eklass == CACHED_CLASS_RAW(Color))
return mono_array_to_PoolColorArray((MonoArray *)p_obj);
- ERR_EXPLAIN(String() + "Attempted to convert a managed array of unmarshallable element type to Variant.");
- ERR_FAIL_V(Variant());
+ ERR_FAIL_V_MSG(Variant(), "Attempted to convert a managed array of unmarshallable element type to Variant.");
} break;
case MONO_TYPE_CLASS: {
@@ -616,7 +815,11 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
// GodotObject
if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) {
Object *ptr = unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_obj));
- return ptr ? Variant(ptr) : Variant();
+ if (ptr != NULL) {
+ Reference *ref = Object::cast_to<Reference>(ptr);
+ return ref ? Variant(Ref<Reference>(ref)) : Variant(ptr);
+ }
+ return Variant();
}
if (CACHED_CLASS(NodePath) == type_class) {
@@ -631,55 +834,78 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
if (CACHED_CLASS(Array) == type_class) {
MonoException *exc = NULL;
- GDMonoUtils::Array_GetPtr get_ptr = CACHED_METHOD_THUNK(Array, GetPtr);
- Array *ptr = get_ptr(p_obj, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, &exc);
+ UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
if (CACHED_CLASS(Dictionary) == type_class) {
MonoException *exc = NULL;
- GDMonoUtils::Dictionary_GetPtr get_ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr);
- Dictionary *ptr = get_ptr(p_obj, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, &exc);
+ UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
- } break;
- case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
+ // The order in which we check the following interfaces is very important (dictionaries and generics first)
- MonoException *exc = NULL;
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
- GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
- MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
+ return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj);
+ }
+
+ if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
+ return GDMonoUtils::Marshal::idictionary_to_dictionary(p_obj);
+ }
+
+ if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) {
+ return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
+ }
+
+ if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
+ return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
+ }
+ } break;
+
+ case MONO_TYPE_GENERICINST: {
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
- if (is_dict) {
+ 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::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
- MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
- UNLIKELY_UNHANDLED_EXCEPTION(exc);
-
- if (is_array) {
+ 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))) {
+ 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))) {
+ return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
+ }
} break;
}
- ERR_EXPLAIN(String() + "Attempted to convert an unmarshallable managed type to Variant. Name: \'" +
- type.type_class->get_name() + "\' Encoding: " + itos(type.type_encoding));
- ERR_FAIL_V(Variant());
+ ERR_FAIL_V_MSG(Variant(), "Attempted to convert an unmarshallable managed type to Variant. Name: '" +
+ type.type_class->get_name() + "' Encoding: " + itos(type.type_encoding) + ".");
}
MonoArray *Array_to_mono_array(const Array &p_array) {
@@ -708,13 +934,13 @@ Array mono_array_to_Array(MonoArray *p_array) {
return ret;
}
-// TODO Optimize reading/writing from/to PoolArrays
-
MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array) {
+ PoolIntArray::Read r = p_array.read();
+
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), p_array.size());
for (int i = 0; i < p_array.size(); i++) {
- mono_array_set(ret, int32_t, i, p_array[i]);
+ mono_array_set(ret, int32_t, i, r[i]);
}
return ret;
@@ -726,19 +952,22 @@ PoolIntArray mono_array_to_PoolIntArray(MonoArray *p_array) {
return ret;
int length = mono_array_length(p_array);
ret.resize(length);
+ PoolIntArray::Write w = ret.write();
+
for (int i = 0; i < length; i++) {
- int32_t elem = mono_array_get(p_array, int32_t, i);
- ret.set(i, elem);
+ w[i] = mono_array_get(p_array, int32_t, i);
}
return ret;
}
MonoArray *PoolByteArray_to_mono_array(const PoolByteArray &p_array) {
+ PoolByteArray::Read r = p_array.read();
+
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), p_array.size());
for (int i = 0; i < p_array.size(); i++) {
- mono_array_set(ret, uint8_t, i, p_array[i]);
+ mono_array_set(ret, uint8_t, i, r[i]);
}
return ret;
@@ -750,20 +979,22 @@ PoolByteArray mono_array_to_PoolByteArray(MonoArray *p_array) {
return ret;
int length = mono_array_length(p_array);
ret.resize(length);
+ PoolByteArray::Write w = ret.write();
for (int i = 0; i < length; i++) {
- uint8_t elem = mono_array_get(p_array, uint8_t, i);
- ret.set(i, elem);
+ w[i] = mono_array_get(p_array, uint8_t, i);
}
return ret;
}
MonoArray *PoolRealArray_to_mono_array(const PoolRealArray &p_array) {
+ PoolRealArray::Read r = p_array.read();
+
MonoArray *ret = mono_array_new(mono_domain_get(), REAL_T_MONOCLASS, p_array.size());
for (int i = 0; i < p_array.size(); i++) {
- mono_array_set(ret, real_t, i, p_array[i]);
+ mono_array_set(ret, real_t, i, r[i]);
}
return ret;
@@ -775,21 +1006,23 @@ PoolRealArray mono_array_to_PoolRealArray(MonoArray *p_array) {
return ret;
int length = mono_array_length(p_array);
ret.resize(length);
+ PoolRealArray::Write w = ret.write();
for (int i = 0; i < length; i++) {
- real_t elem = mono_array_get(p_array, real_t, i);
- ret.set(i, elem);
+ w[i] = mono_array_get(p_array, real_t, i);
}
return ret;
}
MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) {
+ PoolStringArray::Read r = p_array.read();
+
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_array.size());
for (int i = 0; i < p_array.size(); i++) {
- MonoString *boxed = mono_string_from_godot(p_array[i]);
- mono_array_set(ret, MonoString *, i, boxed);
+ MonoString *boxed = mono_string_from_godot(r[i]);
+ mono_array_setref(ret, i, boxed);
}
return ret;
@@ -801,29 +1034,24 @@ PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array) {
return ret;
int length = mono_array_length(p_array);
ret.resize(length);
+ PoolStringArray::Write w = ret.write();
for (int i = 0; i < length; i++) {
MonoString *elem = mono_array_get(p_array, MonoString *, i);
- ret.set(i, mono_string_to_godot(elem));
+ w[i] = mono_string_to_godot(elem);
}
return ret;
}
MonoArray *PoolColorArray_to_mono_array(const PoolColorArray &p_array) {
+ PoolColorArray::Read r = p_array.read();
+
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), p_array.size());
for (int i = 0; i < p_array.size(); i++) {
-#ifdef YOLOCOPY
- mono_array_set(ret, Color, i, p_array[i]);
-#else
- real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 4, i);
- const Color &elem = p_array[i];
- raw[0] = elem.r;
- raw[1] = elem.g;
- raw[2] = elem.b;
- raw[3] = elem.a;
-#endif
+ M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i);
+ *raw = MARSHALLED_OUT(Color, r[i]);
}
return ret;
@@ -835,28 +1063,23 @@ PoolColorArray mono_array_to_PoolColorArray(MonoArray *p_array) {
return ret;
int length = mono_array_length(p_array);
ret.resize(length);
+ PoolColorArray::Write w = ret.write();
for (int i = 0; i < length; i++) {
- real_t *raw_elem = (real_t *)mono_array_addr_with_size(p_array, sizeof(real_t) * 4, i);
- MARSHALLED_IN(Color, raw_elem, elem);
- ret.set(i, elem);
+ w[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i));
}
return ret;
}
MonoArray *PoolVector2Array_to_mono_array(const PoolVector2Array &p_array) {
+ PoolVector2Array::Read r = p_array.read();
+
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), p_array.size());
for (int i = 0; i < p_array.size(); i++) {
-#ifdef YOLOCOPY
- mono_array_set(ret, Vector2, i, p_array[i]);
-#else
- real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 2, i);
- const Vector2 &elem = p_array[i];
- raw[0] = elem.x;
- raw[1] = elem.y;
-#endif
+ M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i);
+ *raw = MARSHALLED_OUT(Vector2, r[i]);
}
return ret;
@@ -868,29 +1091,23 @@ PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array) {
return ret;
int length = mono_array_length(p_array);
ret.resize(length);
+ PoolVector2Array::Write w = ret.write();
for (int i = 0; i < length; i++) {
- real_t *raw_elem = (real_t *)mono_array_addr_with_size(p_array, sizeof(real_t) * 2, i);
- MARSHALLED_IN(Vector2, raw_elem, elem);
- ret.set(i, elem);
+ w[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i));
}
return ret;
}
MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array) {
+ PoolVector3Array::Read r = p_array.read();
+
MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), p_array.size());
for (int i = 0; i < p_array.size(); i++) {
-#ifdef YOLOCOPY
- mono_array_set(ret, Vector3, i, p_array[i]);
-#else
- real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 3, i);
- const Vector3 &elem = p_array[i];
- raw[0] = elem.x;
- raw[1] = elem.y;
- raw[2] = elem.z;
-#endif
+ M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i);
+ *raw = MARSHALLED_OUT(Vector3, r[i]);
}
return ret;
@@ -902,13 +1119,13 @@ PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) {
return ret;
int length = mono_array_length(p_array);
ret.resize(length);
+ PoolVector3Array::Write w = ret.write();
for (int i = 0; i < length; i++) {
- real_t *raw_elem = (real_t *)mono_array_addr_with_size(p_array, sizeof(real_t) * 3, i);
- MARSHALLED_IN(Vector3, raw_elem, elem);
- ret.set(i, elem);
+ w[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i));
}
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 1ad0a4a6ea..3fa958ac32 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.h
+++ b/modules/mono/mono_gd/gd_mono_marshal.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -32,6 +32,7 @@
#define GDMONOMARSHAL_H
#include "core/variant.h"
+
#include "gd_mono.h"
#include "gd_mono_utils.h"
@@ -58,6 +59,9 @@ T unbox(MonoObject *p_obj) {
Variant::Type managed_to_variant_type(const ManagedType &p_type);
+bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type);
+bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type);
+
// String
String mono_to_utf8_string(MonoString *p_mono_string);
@@ -97,10 +101,14 @@ _FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) {
MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type);
MonoObject *variant_to_mono_object(const Variant *p_var);
-_FORCE_INLINE_ MonoObject *variant_to_mono_object(Variant p_var) {
+_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant &p_var) {
return variant_to_mono_object(&p_var);
}
+_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type) {
+ return variant_to_mono_object(&p_var, p_type);
+}
+
Variant mono_object_to_variant(MonoObject *p_obj);
// Array
@@ -143,78 +151,272 @@ PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array);
MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array);
PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array);
-#ifdef YOLO_COPY
-#define MARSHALLED_OUT(m_t, m_in, m_out) m_t *m_out = (m_t *)&m_in;
-#define MARSHALLED_IN(m_t, m_in, m_out) m_t m_out = *reinterpret_cast<m_t *>(m_in);
+// Structures
+
+namespace InteropLayout {
+
+enum {
+ MATCHES_float = (sizeof(float) == sizeof(uint32_t)),
+
+ MATCHES_double = (sizeof(double) == sizeof(uint64_t)),
+
+#ifdef REAL_T_IS_DOUBLE
+ MATCHES_real_t = (sizeof(real_t) == sizeof(uint64_t)),
#else
+ MATCHES_real_t = (sizeof(real_t) == sizeof(uint32_t)),
+#endif
-// Expects m_in to be of type float*
+ MATCHES_Vector2 = (MATCHES_real_t && (sizeof(Vector2) == (sizeof(real_t) * 2)) &&
+ offsetof(Vector2, x) == (sizeof(real_t) * 0) &&
+ offsetof(Vector2, y) == (sizeof(real_t) * 1)),
-#define MARSHALLED_OUT(m_t, m_in, m_out) MARSHALLED_OUT_##m_t(m_in, m_out)
-#define MARSHALLED_IN(m_t, m_in, m_out) MARSHALLED_IN_##m_t(m_in, m_out)
+ MATCHES_Rect2 = (MATCHES_Vector2 && (sizeof(Rect2) == (sizeof(Vector2) * 2)) &&
+ offsetof(Rect2, position) == (sizeof(Vector2) * 0) &&
+ offsetof(Rect2, size) == (sizeof(Vector2) * 1)),
-// Vector2
+ MATCHES_Transform2D = (MATCHES_Vector2 && (sizeof(Transform2D) == (sizeof(Vector2) * 3))), // No field offset required, it stores an array
-#define MARSHALLED_OUT_Vector2(m_in, m_out) real_t m_out[2] = { m_in.x, m_in.y };
-#define MARSHALLED_IN_Vector2(m_in, m_out) Vector2 m_out(m_in[0], m_in[1]);
+ MATCHES_Vector3 = (MATCHES_real_t && (sizeof(Vector3) == (sizeof(real_t) * 3)) &&
+ offsetof(Vector3, x) == (sizeof(real_t) * 0) &&
+ offsetof(Vector3, y) == (sizeof(real_t) * 1) &&
+ offsetof(Vector3, z) == (sizeof(real_t) * 2)),
-// Rect2
+ MATCHES_Basis = (MATCHES_Vector3 && (sizeof(Basis) == (sizeof(Vector3) * 3))), // No field offset required, it stores an array
-#define MARSHALLED_OUT_Rect2(m_in, m_out) real_t m_out[4] = { m_in.position.x, m_in.position.y, m_in.size.width, m_in.size.height };
-#define MARSHALLED_IN_Rect2(m_in, m_out) Rect2 m_out(m_in[0], m_in[1], m_in[2], m_in[3]);
+ MATCHES_Quat = (MATCHES_real_t && (sizeof(Quat) == (sizeof(real_t) * 4)) &&
+ offsetof(Quat, x) == (sizeof(real_t) * 0) &&
+ offsetof(Quat, y) == (sizeof(real_t) * 1) &&
+ offsetof(Quat, z) == (sizeof(real_t) * 2) &&
+ offsetof(Quat, w) == (sizeof(real_t) * 3)),
-// Transform2D
+ MATCHES_Transform = (MATCHES_Basis && MATCHES_Vector3 && (sizeof(Transform) == (sizeof(Basis) + sizeof(Vector3))) &&
+ offsetof(Transform, basis) == 0 &&
+ offsetof(Transform, origin) == sizeof(Basis)),
-#define MARSHALLED_OUT_Transform2D(m_in, m_out) real_t m_out[6] = { m_in[0].x, m_in[0].y, m_in[1].x, m_in[1].y, m_in[2].x, m_in[2].y };
-#define MARSHALLED_IN_Transform2D(m_in, m_out) Transform2D m_out(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5]);
+ MATCHES_AABB = (MATCHES_Vector3 && (sizeof(AABB) == (sizeof(Vector3) * 2)) &&
+ offsetof(AABB, position) == (sizeof(Vector3) * 0) &&
+ offsetof(AABB, size) == (sizeof(Vector3) * 1)),
-// Vector3
+ MATCHES_Color = (MATCHES_float && (sizeof(Color) == (sizeof(float) * 4)) &&
+ offsetof(Color, r) == (sizeof(float) * 0) &&
+ offsetof(Color, g) == (sizeof(float) * 1) &&
+ offsetof(Color, b) == (sizeof(float) * 2) &&
+ offsetof(Color, a) == (sizeof(float) * 3)),
-#define MARSHALLED_OUT_Vector3(m_in, m_out) real_t m_out[3] = { m_in.x, m_in.y, m_in.z };
-#define MARSHALLED_IN_Vector3(m_in, m_out) Vector3 m_out(m_in[0], m_in[1], m_in[2]);
+ MATCHES_Plane = (MATCHES_Vector3 && MATCHES_real_t && (sizeof(Plane) == (sizeof(Vector3) + sizeof(real_t))) &&
+ offsetof(Plane, normal) == 0 &&
+ offsetof(Plane, d) == sizeof(Vector3))
+};
-// Basis
+// In the future we may force this if we want to ref return these structs
+#ifdef GD_MONO_FORCE_INTEROP_STRUCT_COPY
+/* clang-format off */
+GD_STATIC_ASSERT(MATCHES_Vector2 && MATCHES_Rect2 && MATCHES_Transform2D && MATCHES_Vector3 &&
+ MATCHES_Basis && MATCHES_Quat && MATCHES_Transform && MATCHES_AABB && MATCHES_Color &&MATCHES_Plane);
+/* clang-format on */
+#endif
+
+} // namespace InteropLayout
+
+#pragma pack(push, 1)
+
+struct M_Vector2 {
+ real_t x, y;
+
+ static _FORCE_INLINE_ Vector2 convert_to(const M_Vector2 &p_from) {
+ return Vector2(p_from.x, p_from.y);
+ }
-#define MARSHALLED_OUT_Basis(m_in, m_out) real_t m_out[9] = { \
- m_in[0].x, m_in[0].y, m_in[0].z, \
- m_in[1].x, m_in[1].y, m_in[1].z, \
- m_in[2].x, m_in[2].y, m_in[2].z \
+ static _FORCE_INLINE_ M_Vector2 convert_from(const Vector2 &p_from) {
+ M_Vector2 ret = { p_from.x, p_from.y };
+ return ret;
+ }
};
-#define MARSHALLED_IN_Basis(m_in, m_out) Basis m_out(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5], m_in[6], m_in[7], m_in[8]);
-// Quat
+struct M_Rect2 {
+ M_Vector2 position;
+ M_Vector2 size;
-#define MARSHALLED_OUT_Quat(m_in, m_out) real_t m_out[4] = { m_in.x, m_in.y, m_in.z, m_in.w };
-#define MARSHALLED_IN_Quat(m_in, m_out) Quat m_out(m_in[0], m_in[1], m_in[2], m_in[3]);
+ static _FORCE_INLINE_ Rect2 convert_to(const M_Rect2 &p_from) {
+ return Rect2(M_Vector2::convert_to(p_from.position),
+ M_Vector2::convert_to(p_from.size));
+ }
-// Transform
+ static _FORCE_INLINE_ M_Rect2 convert_from(const Rect2 &p_from) {
+ M_Rect2 ret = { M_Vector2::convert_from(p_from.position), M_Vector2::convert_from(p_from.size) };
+ return ret;
+ }
+};
-#define MARSHALLED_OUT_Transform(m_in, m_out) real_t m_out[12] = { \
- m_in.basis[0].x, m_in.basis[0].y, m_in.basis[0].z, \
- m_in.basis[1].x, m_in.basis[1].y, m_in.basis[1].z, \
- m_in.basis[2].x, m_in.basis[2].y, m_in.basis[2].z, \
- m_in.origin.x, m_in.origin.y, m_in.origin.z \
+struct M_Transform2D {
+ M_Vector2 elements[3];
+
+ static _FORCE_INLINE_ Transform2D convert_to(const M_Transform2D &p_from) {
+ return Transform2D(p_from.elements[0].x, p_from.elements[0].y,
+ p_from.elements[1].x, p_from.elements[1].y,
+ p_from.elements[2].x, p_from.elements[2].y);
+ }
+
+ static _FORCE_INLINE_ M_Transform2D convert_from(const Transform2D &p_from) {
+ M_Transform2D ret = {
+ M_Vector2::convert_from(p_from.elements[0]),
+ M_Vector2::convert_from(p_from.elements[1]),
+ M_Vector2::convert_from(p_from.elements[2])
+ };
+ return ret;
+ }
};
-#define MARSHALLED_IN_Transform(m_in, m_out) Transform m_out( \
- Basis(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5], m_in[6], m_in[7], m_in[8]), \
- Vector3(m_in[9], m_in[10], m_in[11]));
-// AABB
+struct M_Vector3 {
+ real_t x, y, z;
-#define MARSHALLED_OUT_AABB(m_in, m_out) real_t m_out[6] = { m_in.position.x, m_in.position.y, m_in.position.z, m_in.size.x, m_in.size.y, m_in.size.z };
-#define MARSHALLED_IN_AABB(m_in, m_out) AABB m_out(Vector3(m_in[0], m_in[1], m_in[2]), Vector3(m_in[3], m_in[4], m_in[5]));
+ static _FORCE_INLINE_ Vector3 convert_to(const M_Vector3 &p_from) {
+ return Vector3(p_from.x, p_from.y, p_from.z);
+ }
-// Color
+ static _FORCE_INLINE_ M_Vector3 convert_from(const Vector3 &p_from) {
+ M_Vector3 ret = { p_from.x, p_from.y, p_from.z };
+ return ret;
+ }
+};
-#define MARSHALLED_OUT_Color(m_in, m_out) real_t m_out[4] = { m_in.r, m_in.g, m_in.b, m_in.a };
-#define MARSHALLED_IN_Color(m_in, m_out) Color m_out(m_in[0], m_in[1], m_in[2], m_in[3]);
+struct M_Basis {
+ M_Vector3 elements[3];
+
+ static _FORCE_INLINE_ Basis convert_to(const M_Basis &p_from) {
+ return Basis(M_Vector3::convert_to(p_from.elements[0]),
+ M_Vector3::convert_to(p_from.elements[1]),
+ M_Vector3::convert_to(p_from.elements[2]));
+ }
+
+ static _FORCE_INLINE_ M_Basis convert_from(const Basis &p_from) {
+ M_Basis ret = {
+ M_Vector3::convert_from(p_from.elements[0]),
+ M_Vector3::convert_from(p_from.elements[1]),
+ M_Vector3::convert_from(p_from.elements[2])
+ };
+ return ret;
+ }
+};
-// Plane
+struct M_Quat {
+ real_t x, y, z, w;
-#define MARSHALLED_OUT_Plane(m_in, m_out) real_t m_out[4] = { m_in.normal.x, m_in.normal.y, m_in.normal.z, m_in.d };
-#define MARSHALLED_IN_Plane(m_in, m_out) Plane m_out(m_in[0], m_in[1], m_in[2], m_in[3]);
+ static _FORCE_INLINE_ Quat convert_to(const M_Quat &p_from) {
+ return Quat(p_from.x, p_from.y, p_from.z, p_from.w);
+ }
-#endif
+ static _FORCE_INLINE_ M_Quat convert_from(const Quat &p_from) {
+ M_Quat ret = { p_from.x, p_from.y, p_from.z, p_from.w };
+ return ret;
+ }
+};
+
+struct M_Transform {
+ M_Basis basis;
+ M_Vector3 origin;
+
+ static _FORCE_INLINE_ Transform convert_to(const M_Transform &p_from) {
+ return Transform(M_Basis::convert_to(p_from.basis), M_Vector3::convert_to(p_from.origin));
+ }
+
+ static _FORCE_INLINE_ M_Transform convert_from(const Transform &p_from) {
+ M_Transform ret = { M_Basis::convert_from(p_from.basis), M_Vector3::convert_from(p_from.origin) };
+ return ret;
+ }
+};
+
+struct M_AABB {
+ M_Vector3 position;
+ M_Vector3 size;
+
+ static _FORCE_INLINE_ AABB convert_to(const M_AABB &p_from) {
+ return AABB(M_Vector3::convert_to(p_from.position), M_Vector3::convert_to(p_from.size));
+ }
+
+ static _FORCE_INLINE_ M_AABB convert_from(const AABB &p_from) {
+ M_AABB ret = { M_Vector3::convert_from(p_from.position), M_Vector3::convert_from(p_from.size) };
+ return ret;
+ }
+};
+
+struct M_Color {
+ float r, g, b, a;
+
+ static _FORCE_INLINE_ Color convert_to(const M_Color &p_from) {
+ return Color(p_from.r, p_from.g, p_from.b, p_from.a);
+ }
+
+ static _FORCE_INLINE_ M_Color convert_from(const Color &p_from) {
+ M_Color ret = { p_from.r, p_from.g, p_from.b, p_from.a };
+ return ret;
+ }
+};
+
+struct M_Plane {
+ M_Vector3 normal;
+ real_t d;
+
+ static _FORCE_INLINE_ Plane convert_to(const M_Plane &p_from) {
+ return Plane(M_Vector3::convert_to(p_from.normal), p_from.d);
+ }
+
+ static _FORCE_INLINE_ M_Plane convert_from(const Plane &p_from) {
+ M_Plane ret = { M_Vector3::convert_from(p_from.normal), p_from.d };
+ return ret;
+ }
+};
+
+#pragma pack(pop)
+
+#define DECL_TYPE_MARSHAL_TEMPLATES(m_type) \
+ template <int> \
+ _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl(const M_##m_type *p_from); \
+ \
+ template <> \
+ _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<0>(const M_##m_type *p_from) { \
+ return M_##m_type::convert_to(*p_from); \
+ } \
+ \
+ template <> \
+ _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<1>(const M_##m_type *p_from) { \
+ return *reinterpret_cast<const m_type *>(p_from); \
+ } \
+ \
+ _FORCE_INLINE_ m_type marshalled_in_##m_type(const M_##m_type *p_from) { \
+ return marshalled_in_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \
+ } \
+ \
+ template <int> \
+ _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl(const m_type &p_from); \
+ \
+ template <> \
+ _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<0>(const m_type &p_from) { \
+ return M_##m_type::convert_from(p_from); \
+ } \
+ \
+ template <> \
+ _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<1>(const m_type &p_from) { \
+ return *reinterpret_cast<const M_##m_type *>(&p_from); \
+ } \
+ \
+ _FORCE_INLINE_ M_##m_type marshalled_out_##m_type(const m_type &p_from) { \
+ return marshalled_out_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \
+ }
+
+DECL_TYPE_MARSHAL_TEMPLATES(Vector2)
+DECL_TYPE_MARSHAL_TEMPLATES(Rect2)
+DECL_TYPE_MARSHAL_TEMPLATES(Transform2D)
+DECL_TYPE_MARSHAL_TEMPLATES(Vector3)
+DECL_TYPE_MARSHAL_TEMPLATES(Basis)
+DECL_TYPE_MARSHAL_TEMPLATES(Quat)
+DECL_TYPE_MARSHAL_TEMPLATES(Transform)
+DECL_TYPE_MARSHAL_TEMPLATES(AABB)
+DECL_TYPE_MARSHAL_TEMPLATES(Color)
+DECL_TYPE_MARSHAL_TEMPLATES(Plane)
+
+#define MARSHALLED_IN(m_type, m_from_ptr) (GDMonoMarshal::marshalled_in_##m_type(m_from_ptr))
+#define MARSHALLED_OUT(m_type, m_from) (GDMonoMarshal::marshalled_out_##m_type(m_from))
} // namespace GDMonoMarshal
diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp
index 630bda8b4e..968b316a3e 100644
--- a/modules/mono/mono_gd/gd_mono_method.cpp
+++ b/modules/mono/mono_gd/gd_mono_method.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -68,26 +68,34 @@ void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) {
param_types.push_back(param_type);
}
+
+ // clear the cache
+ method_info_fetched = false;
+ 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;
}
-GDMonoClassMember::Visibility GDMonoMethod::get_visibility() {
+IMonoClassMember::Visibility GDMonoMethod::get_visibility() {
switch (mono_method_get_flags(mono_method, NULL) & MONO_METHOD_ATTR_ACCESS_MASK) {
case MONO_METHOD_ATTR_PRIVATE:
- return GDMonoClassMember::PRIVATE;
+ return IMonoClassMember::PRIVATE;
case MONO_METHOD_ATTR_FAM_AND_ASSEM:
- return GDMonoClassMember::PROTECTED_AND_INTERNAL;
+ return IMonoClassMember::PROTECTED_AND_INTERNAL;
case MONO_METHOD_ATTR_ASSEM:
- return GDMonoClassMember::INTERNAL;
+ return IMonoClassMember::INTERNAL;
case MONO_METHOD_ATTR_FAMILY:
- return GDMonoClassMember::PROTECTED;
+ return IMonoClassMember::PROTECTED;
case MONO_METHOD_ATTR_PUBLIC:
- return GDMonoClassMember::PUBLIC;
+ return IMonoClassMember::PUBLIC;
default:
- ERR_FAIL_V(GDMonoClassMember::PRIVATE);
+ ERR_FAIL_V(IMonoClassMember::PRIVATE);
}
}
@@ -101,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;
@@ -246,11 +254,34 @@ void GDMonoMethod::get_parameter_types(Vector<ManagedType> &types) const {
}
}
+const MethodInfo &GDMonoMethod::get_method_info() {
+
+ if (!method_info_fetched) {
+ method_info.name = name;
+ method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type), "");
+
+ Vector<StringName> names;
+ get_parameter_names(names);
+
+ for (int i = 0; i < params_count; ++i) {
+ method_info.arguments.push_back(PropertyInfo(GDMonoMarshal::managed_to_variant_type(param_types[i]), names[i]));
+ }
+
+ // TODO: default arguments
+
+ method_info_fetched = true;
+ }
+
+ return method_info;
+}
+
GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) {
name = p_name;
mono_method = p_method;
+ method_info_fetched = false;
+
attrs_fetched = false;
attributes = NULL;
diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h
index 444ec2a67d..2fc8628f27 100644
--- a/modules/mono/mono_gd/gd_mono_method.h
+++ b/modules/mono/mono_gd/gd_mono_method.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -32,10 +32,10 @@
#define GD_MONO_METHOD_H
#include "gd_mono.h"
-#include "gd_mono_class_member.h"
#include "gd_mono_header.h"
+#include "i_mono_class_member.h"
-class GDMonoMethod : public GDMonoClassMember {
+class GDMonoMethod : public IMonoClassMember {
StringName name;
@@ -43,6 +43,9 @@ class GDMonoMethod : public GDMonoClassMember {
ManagedType return_type;
Vector<ManagedType> param_types;
+ bool method_info_fetched;
+ MethodInfo method_info;
+
bool attrs_fetched;
MonoCustomAttrInfo *attributes;
@@ -54,17 +57,19 @@ class GDMonoMethod : public GDMonoClassMember {
MonoMethod *mono_method;
public:
- virtual MemberType get_member_type() { return MEMBER_TYPE_METHOD; }
+ virtual GDMonoClass *get_enclosing_class() const GD_FINAL;
+
+ virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_METHOD; }
- virtual StringName get_name() { return name; }
+ virtual StringName get_name() const GD_FINAL { return name; }
- virtual bool is_static();
+ virtual bool is_static() GD_FINAL;
- virtual Visibility get_visibility();
+ virtual Visibility get_visibility() GD_FINAL;
- virtual bool has_attribute(GDMonoClass *p_attr_class);
- virtual MonoObject *get_attribute(GDMonoClass *p_attr_class);
- virtual void fetch_attributes();
+ virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL;
+ virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL;
+ void fetch_attributes();
_FORCE_INLINE_ int get_parameters_count() { return params_count; }
_FORCE_INLINE_ ManagedType get_return_type() { return return_type; }
@@ -83,6 +88,8 @@ public:
void get_parameter_names(Vector<StringName> &names) const;
void get_parameter_types(Vector<ManagedType> &types) const;
+ const MethodInfo &get_method_info();
+
GDMonoMethod(StringName p_name, MonoMethod *p_method);
~GDMonoMethod();
};
diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp
index ce66e0c8db..f1da00638f 100644
--- a/modules/mono/mono_gd/gd_mono_property.cpp
+++ b/modules/mono/mono_gd/gd_mono_property.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -27,6 +27,7 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+
#include "gd_mono_property.h"
#include "gd_mono_class.h"
@@ -79,24 +80,24 @@ bool GDMonoProperty::is_static() {
return mono_method_get_flags(prop_method, NULL) & MONO_METHOD_ATTR_STATIC;
}
-GDMonoClassMember::Visibility GDMonoProperty::get_visibility() {
+IMonoClassMember::Visibility GDMonoProperty::get_visibility() {
MonoMethod *prop_method = mono_property_get_get_method(mono_property);
if (prop_method == NULL)
prop_method = mono_property_get_set_method(mono_property);
switch (mono_method_get_flags(prop_method, NULL) & MONO_METHOD_ATTR_ACCESS_MASK) {
case MONO_METHOD_ATTR_PRIVATE:
- return GDMonoClassMember::PRIVATE;
+ return IMonoClassMember::PRIVATE;
case MONO_METHOD_ATTR_FAM_AND_ASSEM:
- return GDMonoClassMember::PROTECTED_AND_INTERNAL;
+ return IMonoClassMember::PROTECTED_AND_INTERNAL;
case MONO_METHOD_ATTR_ASSEM:
- return GDMonoClassMember::INTERNAL;
+ return IMonoClassMember::INTERNAL;
case MONO_METHOD_ATTR_FAMILY:
- return GDMonoClassMember::PROTECTED;
+ return IMonoClassMember::PROTECTED;
case MONO_METHOD_ATTR_PUBLIC:
- return GDMonoClassMember::PUBLIC;
+ return IMonoClassMember::PUBLIC;
default:
- ERR_FAIL_V(GDMonoClassMember::PRIVATE);
+ ERR_FAIL_V(IMonoClassMember::PRIVATE);
}
}
@@ -141,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 b3f1e2114a..d6efa60412 100644
--- a/modules/mono/mono_gd/gd_mono_property.h
+++ b/modules/mono/mono_gd/gd_mono_property.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -27,14 +27,15 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+
#ifndef GD_MONO_PROPERTY_H
#define GD_MONO_PROPERTY_H
#include "gd_mono.h"
-#include "gd_mono_class_member.h"
#include "gd_mono_header.h"
+#include "i_mono_class_member.h"
-class GDMonoProperty : public GDMonoClassMember {
+class GDMonoProperty : public IMonoClassMember {
GDMonoClass *owner;
MonoProperty *mono_property;
@@ -46,15 +47,17 @@ class GDMonoProperty : public GDMonoClassMember {
MonoCustomAttrInfo *attributes;
public:
- virtual MemberType get_member_type() { return MEMBER_TYPE_PROPERTY; }
+ virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; }
+
+ virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_PROPERTY; }
- virtual StringName get_name() { return name; }
+ virtual StringName get_name() const GD_FINAL { return name; }
- virtual bool is_static();
- virtual Visibility get_visibility();
+ virtual bool is_static() GD_FINAL;
+ virtual Visibility get_visibility() GD_FINAL;
- virtual bool has_attribute(GDMonoClass *p_attr_class);
- virtual MonoObject *get_attribute(GDMonoClass *p_attr_class);
+ virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL;
+ virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL;
void fetch_attributes();
bool has_getter();
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index b97a24b09c..e385f4c601 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -37,8 +37,13 @@
#include "core/project_settings.h"
#include "core/reference.h"
+#ifdef TOOLS_ENABLED
+#include "editor/script_editor_debugger.h"
+#endif
+
#include "../csharp_script.h"
#include "../utils/macros.h"
+#include "../utils/mutex_utils.h"
#include "gd_mono.h"
#include "gd_mono_class.h"
#include "gd_mono_marshal.h"
@@ -47,13 +52,11 @@ namespace GDMonoUtils {
MonoCache mono_cache;
-#define CACHE_AND_CHECK(m_var, m_val) \
- { \
- m_var = m_val; \
- if (!m_var) { \
- ERR_EXPLAIN("Mono Cache: Member " #m_var " is null"); \
- ERR_FAIL(); \
- } \
+#define CACHE_AND_CHECK(m_var, m_val) \
+ { \
+ CRASH_COND(m_var != NULL); \
+ m_var = m_val; \
+ ERR_FAIL_COND_MSG(!m_var, "Mono Cache: Member " #m_var " is null."); \
}
#define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.class_##m_class, m_val)
@@ -62,8 +65,11 @@ MonoCache mono_cache;
#define CACHE_FIELD_AND_CHECK(m_class, m_field, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.field_##m_class##_##m_field, m_val)
#define CACHE_METHOD_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.method_##m_class##_##m_method, m_val)
#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_corlib_cache() {
-void MonoCache::clear_members() {
+ corlib_cache_updated = false;
class_MonoObject = NULL;
class_bool = NULL;
@@ -80,6 +86,9 @@ void MonoCache::clear_members() {
class_String = NULL;
class_IntPtr = NULL;
+ class_System_Collections_IEnumerable = NULL;
+ class_System_Collections_IDictionary = NULL;
+
#ifdef DEBUG_ENABLED
class_System_Diagnostics_StackTrace = NULL;
methodthunk_System_Diagnostics_StackTrace_GetFrames = NULL;
@@ -88,6 +97,11 @@ void MonoCache::clear_members() {
#endif
class_KeyNotFoundException = NULL;
+}
+
+void MonoCache::clear_godot_api_cache() {
+
+ godot_api_cache_updated = false;
rawclass_Dictionary = NULL;
@@ -104,7 +118,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;
@@ -112,6 +126,7 @@ void MonoCache::clear_members() {
class_Array = NULL;
class_Dictionary = NULL;
class_MarshalUtils = NULL;
+ class_ISerializationListener = NULL;
#ifdef DEBUG_ENABLED
class_DebuggingUtils = NULL;
@@ -142,19 +157,33 @@ void MonoCache::clear_members() {
methodthunk_GodotObject_Dispose = NULL;
methodthunk_Array_GetPtr = NULL;
methodthunk_Dictionary_GetPtr = NULL;
- methodthunk_MarshalUtils_IsArrayGenericType = NULL;
- methodthunk_MarshalUtils_IsDictionaryGenericType = NULL;
methodthunk_SignalAwaiter_SignalCallback = NULL;
methodthunk_SignalAwaiter_FailureCallback = NULL;
methodthunk_GodotTaskScheduler_Activate = NULL;
- task_scheduler_handle = Ref<MonoGCHandle>();
-}
+ // Start of MarshalUtils methods
-void MonoCache::cleanup() {
+ methodthunk_MarshalUtils_TypeIsGenericArray = NULL;
+ methodthunk_MarshalUtils_TypeIsGenericDictionary = NULL;
- corlib_cache_updated = false;
- godot_api_cache_updated = false;
+ 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;
+
+ // End of MarshalUtils methods
+
+ task_scheduler_handle = Ref<MonoGCHandle>();
}
#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class))
@@ -177,6 +206,9 @@ void update_corlib_cache() {
CACHE_CLASS_AND_CHECK(String, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_string_class()));
CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class()));
+ CACHE_CLASS_AND_CHECK(System_Collections_IEnumerable, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IEnumerable"));
+ CACHE_CLASS_AND_CHECK(System_Collections_IDictionary, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IDictionary"));
+
#ifdef DEBUG_ENABLED
CACHE_CLASS_AND_CHECK(System_Diagnostics_StackTrace, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Diagnostics", "StackTrace"));
CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, (StackTrace_GetFrames)CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_thunk("GetFrames"));
@@ -204,7 +236,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));
@@ -212,6 +244,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));
@@ -241,85 +274,110 @@ void update_godot_api_cache() {
CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, (GodotObject_Dispose)CACHED_CLASS(GodotObject)->get_method_thunk("Dispose", 0));
CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, (Array_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method_thunk("GetPtr", 0));
CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, (Dictionary_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method_thunk("GetPtr", 0));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsArrayGenericType, (IsArrayGenericType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IsArrayGenericType", 1));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsDictionaryGenericType, (IsDictionaryGenericType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IsDictionaryGenericType", 1));
CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, (SignalAwaiter_SignalCallback)GODOT_API_CLASS(SignalAwaiter)->get_method_thunk("SignalCallback", 1));
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) {
- if (unmanaged->get_script_instance()) {
- CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance());
- if (cs_instance) {
- return cs_instance->get_mono_object();
- }
+ if (!unmanaged)
+ return NULL;
+
+ if (unmanaged->get_script_instance()) {
+ CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance());
+
+ if (cs_instance) {
+ return cs_instance->get_mono_object();
}
+ }
- // If the owner does not have a CSharpInstance...
+ // If the owner does not have a CSharpInstance...
- void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
+ void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
- if (data) {
- CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value();
+ ERR_FAIL_NULL_V(data, NULL);
- Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
- ERR_FAIL_COND_V(gchandle.is_null(), NULL);
+ CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value();
- MonoObject *target = gchandle->get_target();
+ if (!script_binding.inited) {
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex());
- if (target)
- return target;
+ if (!script_binding.inited) { // Other thread may have set it up
+ // Already had a binding that needs to be setup
+ CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, unmanaged);
- CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
+ ERR_FAIL_COND_V(!script_binding.inited, NULL);
+ }
+ }
- // Create a new one
+ Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ ERR_FAIL_COND_V(gchandle.is_null(), NULL);
-#ifdef DEBUG_ENABLED
- CRASH_COND(script_binding.type_name == StringName());
- CRASH_COND(script_binding.wrapper_class == NULL);
-#endif
+ MonoObject *target = gchandle->get_target();
- MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged);
- ERR_FAIL_NULL_V(mono_object, NULL);
+ if (target)
+ return target;
- gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE);
+ CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
- // Tie managed to unmanaged
- Reference *ref = Object::cast_to<Reference>(unmanaged);
+ // Create a new one
- 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)
+#ifdef DEBUG_ENABLED
+ CRASH_COND(script_binding.type_name == StringName());
+ CRASH_COND(script_binding.wrapper_class == NULL);
+#endif
- ref->reference();
- }
+ MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged);
+ ERR_FAIL_NULL_V(mono_object, NULL);
- return mono_object;
- }
+ gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE);
+
+ // Tie managed to unmanaged
+ Reference *ref = Object::cast_to<Reference>(unmanaged);
+
+ 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)
+ ref->reference();
}
- return NULL;
+ return mono_object;
}
void set_main_thread(MonoThread *p_thread) {
@@ -328,7 +386,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);
}
@@ -343,11 +401,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) {
@@ -362,6 +419,11 @@ GDMonoClass *type_get_proxy_class(const StringName &p_type) {
GDMonoClass *klass = GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name);
+ if (klass && klass->is_static()) {
+ // A static class means this is a Godot singleton class. If an instance is needed we use Godot.Object.
+ return mono_cache.class_GodotObject;
+ }
+
#ifdef TOOLS_ENABLED
if (!klass) {
return GDMono::get_singleton()->get_editor_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name);
@@ -388,33 +450,27 @@ 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)) {
- 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);
- }
+ bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), p_native);
+ ERR_FAIL_COND_V_MSG(!parent_is_object_class, NULL,
+ "Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'.");
- 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)));
@@ -422,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)));
@@ -434,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
@@ -458,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
@@ -488,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;
}
@@ -540,8 +596,14 @@ void debug_print_unhandled_exception(MonoException *p_exc) {
void debug_send_unhandled_exception_error(MonoException *p_exc) {
#ifdef DEBUG_ENABLED
- if (!ScriptDebugger::get_singleton())
+ if (!ScriptDebugger::get_singleton()) {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ ERR_PRINTS(GDMonoUtils::get_exception_name_and_message(p_exc));
+ }
+#endif
return;
+ }
_TLS_RECURSION_GUARD_;
@@ -565,7 +627,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
if (unexpected_exc) {
GDMonoInternals::unhandled_exception(unexpected_exc);
- _UNREACHABLE_();
+ return;
}
Vector<ScriptLanguage::StackInfo> _si;
@@ -598,13 +660,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
}
void debug_unhandled_exception(MonoException *p_exc) {
-#ifdef DEBUG_ENABLED
- GDMonoUtils::debug_send_unhandled_exception_error(p_exc);
- if (ScriptDebugger::get_singleton())
- ScriptDebugger::get_singleton()->idle_poll();
-#endif
GDMonoInternals::unhandled_exception(p_exc); // prints the exception as well
- _UNREACHABLE_();
}
void print_unhandled_exception(MonoException *p_exc) {
@@ -612,55 +668,53 @@ 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);
+#else
if (get_runtime_invoke_count() == 0) {
debug_unhandled_exception(p_exc);
- _UNREACHABLE_();
}
if (!mono_runtime_set_pending_exception(p_exc, false)) {
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);
- _UNREACHABLE_();
#endif
}
_THREAD_LOCAL_(int)
current_invoke_count = 0;
-MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **p_exc) {
+MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)p_exc);
+ MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)r_exc);
GD_MONO_END_RUNTIME_INVOKE;
return ret;
}
-MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **p_exc) {
+MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = mono_runtime_invoke_array(p_method, p_obj, p_params, (MonoObject **)p_exc);
+ MonoObject *ret = mono_runtime_invoke_array(p_method, p_obj, p_params, (MonoObject **)r_exc);
GD_MONO_END_RUNTIME_INVOKE;
return ret;
}
-MonoString *object_to_string(MonoObject *p_obj, MonoException **p_exc) {
+MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)p_exc);
+ MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)r_exc);
GD_MONO_END_RUNTIME_INVOKE;
return ret;
}
-void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc) {
+void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
- mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)p_exc);
+ mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)r_exc);
GD_MONO_END_RUNTIME_INVOKE;
}
-MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc) {
+MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)p_exc);
+ MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)r_exc);
GD_MONO_END_RUNTIME_INVOKE;
return ret;
}
@@ -694,4 +748,143 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &
}
}
+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 ec3a57eb46..d73743bf0b 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -41,26 +41,64 @@
#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); \
- _UNREACHABLE_(); \
+ GD_UNREACHABLE(); \
}
namespace GDMonoUtils {
-typedef void (*GodotObject_Dispose)(MonoObject *, MonoObject **);
-typedef Array *(*Array_GetPtr)(MonoObject *, MonoObject **);
-typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoObject **);
-typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoObject **);
-typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoObject **);
-typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoObject **);
-typedef MonoArray *(*StackTrace_GetFrames)(MonoObject *, MonoObject **);
-typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **);
-typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **);
-typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **);
-typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **);
-typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, MonoString **, MonoObject **);
+typedef void (*GodotObject_Dispose)(MonoObject *, MonoException **);
+typedef Array *(*Array_GetPtr)(MonoObject *, MonoException **);
+typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoException **);
+typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoException **);
+typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoException **);
+typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoException **);
+typedef MonoArray *(*StackTrace_GetFrames)(MonoObject *, MonoException **);
+typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, MonoString **, MonoException **);
+
+typedef MonoBoolean (*TypeIsGenericArray)(MonoReflectionType *, MonoException **);
+typedef MonoBoolean (*TypeIsGenericDictionary)(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 {
@@ -83,6 +121,9 @@ struct MonoCache {
GDMonoClass *class_String;
GDMonoClass *class_IntPtr;
+ GDMonoClass *class_System_Collections_IEnumerable;
+ GDMonoClass *class_System_Collections_IDictionary;
+
#ifdef DEBUG_ENABLED
GDMonoClass *class_System_Diagnostics_StackTrace;
StackTrace_GetFrames methodthunk_System_Diagnostics_StackTrace_GetFrames;
@@ -108,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;
@@ -116,6 +157,7 @@ struct MonoCache {
GDMonoClass *class_Array;
GDMonoClass *class_Dictionary;
GDMonoClass *class_MarshalUtils;
+ GDMonoClass *class_ISerializationListener;
#ifdef DEBUG_ENABLED
GDMonoClass *class_DebuggingUtils;
@@ -146,25 +188,43 @@ struct MonoCache {
GodotObject_Dispose methodthunk_GodotObject_Dispose;
Array_GetPtr methodthunk_Array_GetPtr;
Dictionary_GetPtr methodthunk_Dictionary_GetPtr;
- IsArrayGenericType methodthunk_MarshalUtils_IsArrayGenericType;
- IsDictionaryGenericType methodthunk_MarshalUtils_IsDictionaryGenericType;
SignalAwaiter_SignalCallback methodthunk_SignalAwaiter_SignalCallback;
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();
}
};
@@ -172,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);
@@ -194,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);
@@ -214,7 +289,7 @@ void set_exception_message(MonoException *p_exc, String message);
void debug_print_unhandled_exception(MonoException *p_exc);
void debug_send_unhandled_exception_error(MonoException *p_exc);
-_NO_RETURN_ void debug_unhandled_exception(MonoException *p_exc);
+void debug_unhandled_exception(MonoException *p_exc);
void print_unhandled_exception(MonoException *p_exc);
/**
@@ -233,16 +308,18 @@ _FORCE_INLINE_ int &get_runtime_invoke_count_ref() {
return current_invoke_count;
}
-MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **p_exc);
-MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **p_exc);
+MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc);
+MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc);
-MonoString *object_to_string(MonoObject *p_obj, MonoException **p_exc);
+MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc);
-void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc);
-MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc);
+void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc);
+MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc);
uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error);
+void dispose(MonoObject *p_mono_object, MonoException **r_exc);
+
} // namespace GDMonoUtils
#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL)))
@@ -253,6 +330,7 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &
#define CACHED_FIELD(m_class, m_field) (GDMonoUtils::mono_cache.field_##m_class##_##m_field)
#define CACHED_METHOD(m_class, m_method) (GDMonoUtils::mono_cache.method_##m_class##_##m_method)
#define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method)
+#define CACHED_PROPERTY(m_class, m_property) (GDMonoUtils::mono_cache.property_##m_class##_##m_property)
#ifdef REAL_T_IS_DOUBLE
#define REAL_T_MONOCLASS CACHED_CLASS_RAW(double)
@@ -267,4 +345,93 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &
#define GD_MONO_END_RUNTIME_INVOKE \
_runtime_invoke_count_ref -= 1;
+inline void invoke_method_thunk(void (*p_method_thunk)()) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ p_method_thunk();
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R>
+R invoke_method_thunk(R (*p_method_thunk)()) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ R r = p_method_thunk();
+ GD_MONO_END_RUNTIME_INVOKE;
+ return r;
+}
+
+template <class P1>
+void invoke_method_thunk(void (*p_method_thunk)(P1), P1 p_arg1) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ p_method_thunk(p_arg1);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1>
+R invoke_method_thunk(R (*p_method_thunk)(P1), P1 p_arg1) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ R r = p_method_thunk(p_arg1);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return r;
+}
+
+template <class P1, class P2>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ p_method_thunk(p_arg1, p_arg2);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ R r = p_method_thunk(p_arg1, p_arg2);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return r;
+}
+
+template <class P1, class P2, class P3>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ p_method_thunk(p_arg1, p_arg2, p_arg3);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2, class P3>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ R r = p_method_thunk(p_arg1, p_arg2, p_arg3);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return r;
+}
+
+template <class P1, class P2, class P3, class P4>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2, class P3, class P4>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return r;
+}
+
+template <class P1, class P2, class P3, class P4, class P5>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2, class P3, class P4, class P5>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return r;
+}
+
#endif // GD_MONOUTILS_H
diff --git a/modules/mono/mono_gd/gd_mono_class_member.h b/modules/mono/mono_gd/i_mono_class_member.h
index 008ea0e416..f4de4e3230 100644
--- a/modules/mono/mono_gd/gd_mono_class_member.h
+++ b/modules/mono/mono_gd/i_mono_class_member.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* gd_mono_class_member.h */
+/* i_mono_class_member.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -27,14 +27,15 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef GD_MONO_CLASS_MEMBER_H
-#define GD_MONO_CLASS_MEMBER_H
+
+#ifndef I_MONO_CLASS_MEMBER_H
+#define I_MONO_CLASS_MEMBER_H
#include "gd_mono_header.h"
#include <mono/metadata/object.h>
-class GDMonoClassMember {
+class IMonoClassMember {
public:
enum Visibility {
PRIVATE,
@@ -50,11 +51,13 @@ public:
MEMBER_TYPE_METHOD
};
- virtual ~GDMonoClassMember() {}
+ virtual ~IMonoClassMember() {}
+
+ virtual GDMonoClass *get_enclosing_class() const = 0;
- virtual MemberType get_member_type() = 0;
+ virtual MemberType get_member_type() const = 0;
- virtual StringName get_name() = 0;
+ virtual StringName get_name() const = 0;
virtual bool is_static() = 0;
@@ -64,4 +67,4 @@ public:
virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) = 0;
};
-#endif // GD_MONO_CLASS_MEMBER_H
+#endif // I_MONO_CLASS_MEMBER_H
diff --git a/modules/mono/editor/mono_build_info.cpp b/modules/mono/mono_gd/managed_type.cpp
index e4af2aac4f..9f736b71cd 100644
--- a/modules/mono/editor/mono_build_info.cpp
+++ b/modules/mono/mono_gd/managed_type.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* mono_build_info.cpp */
+/* managed_type.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -28,35 +28,31 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "mono_build_info.h"
+#include "managed_type.h"
-#include "../godotsharp_dirs.h"
-#include "../mono_gd/gd_mono_utils.h"
+#include "gd_mono.h"
+#include "gd_mono_class.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;
+ManagedType ManagedType::from_class(GDMonoClass *p_class) {
+ return ManagedType(mono_type_get_type(p_class->get_mono_type()), p_class);
}
-bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const {
+ManagedType ManagedType::from_class(MonoClass *p_mono_class) {
+ GDMonoClass *tclass = GDMono::get_singleton()->get_class(p_mono_class);
+ ERR_FAIL_COND_V(!tclass, ManagedType());
- return p_b.solution == solution && p_b.configuration == configuration;
+ return ManagedType(mono_type_get_type(tclass->get_mono_type()), tclass);
}
-String MonoBuildInfo::get_log_dirpath() {
+ManagedType ManagedType::from_type(MonoType *p_mono_type) {
+ MonoClass *mono_class = mono_class_from_mono_type(p_mono_type);
+ GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_class);
+ ERR_FAIL_COND_V(!tclass, ManagedType());
- return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration);
+ return ManagedType(mono_type_get_type(p_mono_type), tclass);
}
-MonoBuildInfo::MonoBuildInfo() {}
-
-MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) {
-
- solution = p_solution;
- configuration = p_config;
+ManagedType ManagedType::from_reftype(MonoReflectionType *p_mono_reftype) {
+ MonoType *mono_type = mono_reflection_type_get_type(p_mono_reftype);
+ return from_type(mono_type);
}
diff --git a/modules/mono/mono_gd/managed_type.h b/modules/mono/mono_gd/managed_type.h
new file mode 100644
index 0000000000..a537e56aea
--- /dev/null
+++ b/modules/mono/mono_gd/managed_type.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* managed_type.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 MANAGED_TYPE_H
+#define MANAGED_TYPE_H
+
+#include <mono/metadata/object.h>
+
+#include "gd_mono_header.h"
+
+struct ManagedType {
+ int type_encoding;
+ GDMonoClass *type_class;
+
+ static ManagedType from_class(GDMonoClass *p_class);
+ static ManagedType from_class(MonoClass *p_mono_class);
+ static ManagedType from_type(MonoType *p_mono_type);
+ static ManagedType from_reftype(MonoReflectionType *p_mono_reftype);
+
+ ManagedType() :
+ type_encoding(0),
+ type_class(NULL) {
+ }
+
+ ManagedType(int p_type_encoding, GDMonoClass *p_type_class) :
+ type_encoding(p_type_encoding),
+ type_class(p_type_class) {
+ }
+};
+
+#endif // MANAGED_TYPE_H
diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp
index f6cb143e8e..3607b6f8b3 100644
--- a/modules/mono/register_types.cpp
+++ b/modules/mono/register_types.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -35,8 +35,8 @@
#include "csharp_script.h"
CSharpLanguage *script_language_cs = NULL;
-ResourceFormatLoaderCSharpScript *resource_loader_cs = NULL;
-ResourceFormatSaverCSharpScript *resource_saver_cs = NULL;
+Ref<ResourceFormatLoaderCSharpScript> resource_loader_cs;
+Ref<ResourceFormatSaverCSharpScript> resource_saver_cs;
_GodotSharp *_godotsharp = NULL;
@@ -52,9 +52,10 @@ void register_mono_types() {
script_language_cs->set_language_index(ScriptServer::get_language_count());
ScriptServer::register_language(script_language_cs);
- resource_loader_cs = memnew(ResourceFormatLoaderCSharpScript);
+ resource_loader_cs.instance();
ResourceLoader::add_resource_format_loader(resource_loader_cs);
- resource_saver_cs = memnew(ResourceFormatSaverCSharpScript);
+
+ resource_saver_cs.instance();
ResourceSaver::add_resource_format_saver(resource_saver_cs);
}
@@ -63,10 +64,12 @@ void unregister_mono_types() {
if (script_language_cs)
memdelete(script_language_cs);
- if (resource_loader_cs)
- memdelete(resource_loader_cs);
- if (resource_saver_cs)
- memdelete(resource_saver_cs);
+
+ ResourceLoader::remove_resource_format_loader(resource_loader_cs);
+ resource_loader_cs.unref();
+
+ ResourceSaver::remove_resource_format_saver(resource_saver_cs);
+ resource_saver_cs.unref();
if (_godotsharp)
memdelete(_godotsharp);
diff --git a/modules/mono/register_types.h b/modules/mono/register_types.h
index ab8a7d6463..0601e9a382 100644
--- a/modules/mono/register_types.h
+++ b/modules/mono/register_types.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp
index b4c78df538..189ceaab1b 100644
--- a/modules/mono/signal_awaiter_utils.cpp
+++ b/modules/mono/signal_awaiter_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -67,10 +67,8 @@ Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p
Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
#ifdef DEBUG_ENABLED
- if (conn_target_id && !ObjectDB::get_instance(conn_target_id)) {
- ERR_EXPLAIN("Resumed after await, but class instance is gone");
- ERR_FAIL_V(Variant());
- }
+ ERR_FAIL_COND_V_MSG(conn_target_id && !ObjectDB::get_instance(conn_target_id), Variant(),
+ "Resumed after await, but class instance is gone.");
#endif
if (p_argcount < 1) {
@@ -91,18 +89,16 @@ 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);
}
- GDMonoUtils::SignalAwaiter_SignalCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback);
-
MonoException *exc = NULL;
GD_MONO_BEGIN_RUNTIME_INVOKE;
- thunk(get_target(), signal_args, (MonoObject **)&exc);
+ invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback), get_target(), signal_args, &exc);
GD_MONO_END_RUNTIME_INVOKE;
if (exc) {
@@ -129,19 +125,17 @@ SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) :
SignalAwaiterHandle::~SignalAwaiterHandle() {
if (!completed) {
- GDMonoUtils::SignalAwaiter_FailureCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback);
-
MonoObject *awaiter = get_target();
if (awaiter) {
MonoException *exc = NULL;
GD_MONO_BEGIN_RUNTIME_INVOKE;
- thunk(awaiter, (MonoObject **)&exc);
+ invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback), awaiter, &exc);
GD_MONO_END_RUNTIME_INVOKE;
if (exc) {
GDMonoUtils::set_pending_exception(exc);
- ERR_FAIL_V();
+ ERR_FAIL();
}
}
}
diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h
index 4ec860537b..4fb3cdb56d 100644
--- a/modules/mono/signal_awaiter_utils.h
+++ b/modules/mono/signal_awaiter_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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 1d858d80bf..7dd67e3b8e 100644
--- a/modules/mono/editor/monodevelop_instance.cpp
+++ b/modules/mono/utils/android_utils.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* monodevelop_instance.cpp */
+/* android_utils.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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 64ba0f4037..f911c3fdfe 100644
--- a/modules/mono/editor/mono_build_info.h
+++ b/modules/mono/utils/android_utils.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* mono_build_info.h */
+/* android_utils.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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/macros.h b/modules/mono/utils/macros.h
index 337a86870e..e44f254e1c 100644
--- a/modules/mono/utils/macros.h
+++ b/modules/mono/utils/macros.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* util_macros.h */
+/* macros.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -31,28 +31,53 @@
#ifndef UTIL_MACROS_H
#define UTIL_MACROS_H
-// noreturn
+#define _GD_VARNAME_CONCAT_B_(m_ignore, m_name) m_name
+#define _GD_VARNAME_CONCAT_A_(m_a, m_b, m_c) _GD_VARNAME_CONCAT_B_(hello there, m_a##m_b##m_c)
+#define _GD_VARNAME_CONCAT_(m_a, m_b, m_c) _GD_VARNAME_CONCAT_A_(m_a, m_b, m_c)
+#define GD_UNIQUE_NAME(m_name) _GD_VARNAME_CONCAT_(m_name, _, __COUNTER__)
+
+// static assert
+// TODO: Get rid of this macro once we upgrade to C++11
+
+#ifdef __cpp_static_assert
+#define GD_STATIC_ASSERT(m_cond) static_assert((m_cond), "Condition '" #m_cond "' failed")
+#else
+#define GD_STATIC_ASSERT(m_cond) typedef int GD_UNIQUE_NAME(godot_static_assert)[((m_cond) ? 1 : -1)]
+#endif
-#undef _NO_RETURN_
+// final
+// TODO: Get rid of this macro once we upgrade to C++11
+
+#if (__cplusplus >= 201103L)
+#define GD_FINAL final
+#else
+#define GD_FINAL
+#endif
+
+// noreturn
+// TODO: Get rid of this macro once we upgrade to C++11
-#ifdef __GNUC__
-#define _NO_RETURN_ __attribute__((noreturn))
-#elif _MSC_VER
-#define _NO_RETURN_ __declspec(noreturn)
+#if (__cplusplus >= 201103L)
+#define GD_NORETURN [[noreturn]]
+#elif defined(__GNUC__)
+#define GD_NORETURN __attribute__((noreturn))
+#elif defined(_MSC_VER)
+#define GD_NORETURN __declspec(noreturn)
#else
-#error Platform or compiler not supported
+#define GD_NORETURN
+#pragma message "Macro GD_NORETURN will have no effect"
#endif
// unreachable
#if defined(_MSC_VER)
-#define _UNREACHABLE_() __assume(0)
+#define GD_UNREACHABLE() __assume(0)
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405
-#define _UNREACHABLE_() __builtin_unreachable()
+#define GD_UNREACHABLE() __builtin_unreachable()
#else
-#define _UNREACHABLE_() \
- CRASH_NOW(); \
- do { \
+#define GD_UNREACHABLE() \
+ CRASH_NOW(); \
+ do { \
} while (true);
#endif
diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp
index 8116df5f51..98aeadc8c8 100644
--- a/modules/mono/utils/mono_reg_utils.cpp
+++ b/modules/mono/utils/mono_reg_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "mono_reg_utils.h"
+#include "core/os/dir_access.h"
#ifdef WINDOWS_ENABLED
@@ -158,8 +159,6 @@ MonoRegInfo find_mono() {
if (_find_mono_in_reg_old("Software\\Novell\\Mono", info) == ERROR_SUCCESS)
return info;
- ERR_PRINT("Cannot find mono in the registry");
-
return MonoRegInfo();
}
@@ -202,6 +201,13 @@ String find_msbuild_tools_path() {
val += "\\";
}
+ // Since VS2019, the directory is simply named "Current"
+ String msbuild_dir = val + "MSBuild\\Current\\Bin";
+ if (DirAccess::exists(msbuild_dir)) {
+ return msbuild_dir;
+ }
+
+ // Directory name "15.0" is used in VS 2017
return val + "MSBuild\\15.0\\Bin";
}
}
@@ -228,4 +234,4 @@ cleanup:
}
} // namespace MonoRegUtils
-#endif WINDOWS_ENABLED
+#endif // WINDOWS_ENABLED
diff --git a/modules/mono/utils/mono_reg_utils.h b/modules/mono/utils/mono_reg_utils.h
index 26f7e2d3c2..25446a4992 100644
--- a/modules/mono/utils/mono_reg_utils.h
+++ b/modules/mono/utils/mono_reg_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
diff --git a/modules/mono/utils/mutex_utils.h b/modules/mono/utils/mutex_utils.h
new file mode 100644
index 0000000000..b8be033cba
--- /dev/null
+++ b/modules/mono/utils/mutex_utils.h
@@ -0,0 +1,67 @@
+/*************************************************************************/
+/* mutex_utils.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 MUTEX_UTILS_H
+#define MUTEX_UTILS_H
+
+#include "core/error_macros.h"
+#include "core/os/mutex.h"
+
+#include "macros.h"
+
+class ScopedMutexLock {
+ Mutex *mutex;
+
+public:
+ ScopedMutexLock(Mutex *mutex) {
+ this->mutex = mutex;
+#ifndef NO_THREADS
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!mutex);
+#endif
+ this->mutex->lock();
+#endif
+ }
+
+ ~ScopedMutexLock() {
+#ifndef NO_THREADS
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!mutex);
+#endif
+ mutex->unlock();
+#endif
+ }
+};
+
+#define SCOPED_MUTEX_LOCK(m_mutex) ScopedMutexLock GD_UNIQUE_NAME(__scoped_mutex_lock__)(m_mutex);
+
+// TODO: Add version that receives a lambda instead, once C++11 is allowed
+
+#endif // MUTEX_UTILS_H
diff --git a/modules/mono/utils/osx_utils.cpp b/modules/mono/utils/osx_utils.cpp
index f520706a0c..f1362be249 100644
--- a/modules/mono/utils/osx_utils.cpp
+++ b/modules/mono/utils/osx_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -30,10 +30,10 @@
#include "osx_utils.h"
-#include "core/print_string.h"
-
#ifdef OSX_ENABLED
+#include "core/print_string.h"
+
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
diff --git a/modules/mono/utils/osx_utils.h b/modules/mono/utils/osx_utils.h
index 68479b4f44..cc72233058 100644
--- a/modules/mono/utils/osx_utils.h
+++ b/modules/mono/utils/osx_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -31,6 +31,7 @@
#include "core/ustring.h"
#ifndef OSX_UTILS_H
+#define OSX_UTILS_H
#ifdef OSX_ENABLED
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index ea942a9a8e..20863b1afe 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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,11 +60,11 @@ 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++) {
- String p2 = p + exts[j];
+ String p2 = p + exts[j].to_lower(); // lowercase to reduce risk of case mismatch warning
if (FileAccess::exists(p2))
return p2;
@@ -73,40 +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 ret[PATH_MAX];
- if (realpath(p_existing_path.utf8().get_data(), ret)) {
- String retstr;
- if (!retstr.parse_utf8(ret)) {
- 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 3c7b36c0d4..ca25bc09f7 100644
--- a/modules/mono/utils/path_utils.h
+++ b/modules/mono/utils/path_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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 8691932f9a..e9efc7626d 100644
--- a/modules/mono/utils/string_utils.cpp
+++ b/modules/mono/utils/string_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -30,6 +30,11 @@
#include "string_utils.h"
+#include "core/os/file_access.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
namespace {
int sfind(const String &p_text, int p_from) {
@@ -39,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();
@@ -50,10 +55,7 @@ int sfind(const String &p_text, int p_from) {
for (int j = 0; j < src_len; j++) {
int read_pos = i + j;
- if (read_pos >= len) {
- ERR_PRINT("read_pos >= len");
- return -1;
- };
+ ERR_FAIL_COND_V(read_pos >= len, -1);
switch (j) {
case 0:
@@ -61,7 +63,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:
@@ -128,6 +130,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const
return new_string;
}
+#ifdef TOOLS_ENABLED
bool is_csharp_keyword(const String &p_name) {
// Reserved keywords
@@ -156,3 +159,79 @@ bool is_csharp_keyword(const String &p_name) {
String escape_csharp_keyword(const String &p_name) {
return is_csharp_keyword(p_name) ? "@" + p_name : p_name;
}
+#endif
+
+Error read_all_file_utf8(const String &p_path, String &r_content) {
+ PoolVector<uint8_t> sourcef;
+ Error err;
+ FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+
+ int len = f->get_len();
+ sourcef.resize(len + 1);
+ PoolVector<uint8_t>::Write w = sourcef.write();
+ int r = f->get_buffer(w.ptr(), len);
+ f->close();
+ memdelete(f);
+ ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN);
+ w[len] = 0;
+
+ String source;
+ if (source.parse_utf8((const char *)w.ptr())) {
+ ERR_FAIL_V(ERR_INVALID_DATA);
+ }
+
+ 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 gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_args_copy)
+#define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy)
+#else
+#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf(m_buffer, m_count, m_format, m_args_copy)
+#define gd_vscprintf(m_format, m_args_copy) vsnprintf(NULL, 0, p_format, m_args_copy)
+#endif
+
+String str_format(const char *p_format, va_list p_list) {
+ va_list list;
+
+ va_copy(list, p_list);
+ int len = gd_vscprintf(p_format, list);
+ va_end(list);
+
+ len += 1; // for the trailing '/0'
+
+ char *buffer(memnew_arr(char, len));
+
+ va_copy(list, p_list);
+ gd_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 f2df2340ae..565b9bb644 100644
--- a/modules/mono/utils/string_utils.h
+++ b/modules/mono/utils/string_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -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
@@ -42,4 +44,17 @@ bool is_csharp_keyword(const String &p_name);
String escape_csharp_keyword(const String &p_name);
#endif
+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.cpp b/modules/mono/utils/thread_local.cpp
index a0e28fca5f..13179bf408 100644
--- a/modules/mono/utils/thread_local.cpp
+++ b/modules/mono/utils/thread_local.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
diff --git a/modules/mono/utils/thread_local.h b/modules/mono/utils/thread_local.h
index 84dae1d86b..e52b6e73ef 100644
--- a/modules/mono/utils/thread_local.h
+++ b/modules/mono/utils/thread_local.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* 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 */
@@ -39,7 +39,7 @@
#error Platform or compiler not supported
#endif
-#ifdef __GNUC__
+#if defined(__GNUC__)
#ifdef HAVE_GCC___THREAD
#define _THREAD_LOCAL_(m_t) __thread m_t
@@ -47,7 +47,7 @@
#define USE_CUSTOM_THREAD_LOCAL
#endif
-#elif _MSC_VER
+#elif defined(_MSC_VER)
#ifdef HAVE_DECLSPEC_THREAD
#define _THREAD_LOCAL_(m_t) __declspec(thread) m_t
@@ -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:
@@ -108,17 +108,23 @@ class ThreadLocal {
return data;
}
+ void _initialize(const T &p_init_val) {
+ init_val = p_init_val;
+ storage.alloc(&destr_callback);
+ }
+
public:
- ThreadLocal() :
- ThreadLocal(T()) {}
+ ThreadLocal() {
+ _initialize(T());
+ }
- ThreadLocal(const T &p_init_val) :
- init_val(p_init_val) {
- storage.alloc(&destr_callback);
+ ThreadLocal(const T &p_init_val) {
+ _initialize(p_init_val);
}
- ThreadLocal(const ThreadLocal &other) :
- ThreadLocal(*other._tls_get_value()) {}
+ ThreadLocal(const ThreadLocal &other) {
+ _initialize(*other._tls_get_value());
+ }
~ThreadLocal() {
storage.free();