diff options
Diffstat (limited to 'modules/mono')
133 files changed, 7798 insertions, 4982 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub index 341d57f3e4..cc60e64a11 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -1,5 +1,8 @@ #!/usr/bin/env python +import build_scripts.tls_configure as tls_configure +import build_scripts.mono_configure as mono_configure + Import('env') Import('env_modules') @@ -20,38 +23,42 @@ if env['tools']: 'glue/cs_glue_version.gen.h' ) -vars = Variables() -vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True)) -vars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False)) -vars.Update(env_mono) - # Glue sources if env_mono['mono_glue']: env_mono.Append(CPPDEFINES=['MONO_GLUE_ENABLED']) import os.path if not os.path.isfile('glue/mono_glue.gen.cpp'): - raise RuntimeError('Missing mono glue sources. Did you forget to generate them?') + raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?") if env_mono['tools'] or env_mono['target'] != 'release': env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD']) # Configure Thread Local Storage -import build_scripts.tls_configure as tls_configure - conf = Configure(env_mono) tls_configure.configure(conf) env_mono = conf.Finish() # Configure Mono -import build_scripts.mono_configure as mono_configure - mono_configure.configure(env, env_mono) -# Build GodotSharpTools +# Build Godot API solution + +if env_mono['tools'] and env_mono['mono_glue']: + import build_scripts.api_solution_build as api_solution_build + api_solution_build.build(env_mono) -import build_scripts.godotsharptools_build as godotsharptools_build +# Build GodotTools -godotsharptools_build.build(env_mono) +if env_mono['tools']: + import build_scripts.godot_tools_build as godot_tools_build + if env_mono['mono_glue']: + godot_tools_build.build(env_mono) + else: + # Building without the glue sources so the Godot API solution may be missing. + # GodotTools depends on the Godot API solution. As such, we will only build + # GodotTools.ProjectEditor which doesn't depend on the Godot API solution and + # is required by the bindings generator in order to be able to generated it. + godot_tools_build.build_project_editor_only(env_mono) diff --git a/modules/mono/__init__.py b/modules/mono/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/mono/__init__.py diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py new file mode 100644 index 0000000000..1fe00a3028 --- /dev/null +++ b/modules/mono/build_scripts/api_solution_build.py @@ -0,0 +1,66 @@ +# Build the Godot API solution + +import os + +from SCons.Script import Dir + + +def build_api_solution(source, target, env): + # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str + + module_dir = env['module_dir'] + + solution_path = os.path.join(module_dir, 'glue/Managed/Generated/GodotSharp.sln') + + if not os.path.isfile(solution_path): + raise RuntimeError("Godot API solution not found. Did you forget to run '--generate-mono-glue'?") + + build_config = env['solution_build_config'] + + extra_msbuild_args = ['/p:NoWarn=1591'] # Ignore missing documentation warnings + + from .solution_builder import build_solution + build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args) + + # Copy targets + + core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharp', 'bin', build_config)) + editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharpEditor', 'bin', build_config)) + + dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + + if not os.path.isdir(dst_dir): + assert not os.path.isfile(dst_dir) + os.makedirs(dst_dir) + + def copy_target(target_path): + from shutil import copy + filename = os.path.basename(target_path) + + src_path = os.path.join(core_src_dir, filename) + if not os.path.isfile(src_path): + src_path = os.path.join(editor_src_dir, filename) + + copy(src_path, target_path) + + for scons_target in target: + copy_target(str(scons_target)) + + +def build(env_mono): + assert env_mono['tools'] + + target_filenames = [ + 'GodotSharp.dll', 'GodotSharp.pdb', 'GodotSharp.xml', + 'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml' + ] + + for build_config in ['Debug', 'Release']: + output_dir = Dir('#bin').abspath + editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', build_config) + + targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames] + + cmd = env_mono.CommandNoCache(targets, [], build_api_solution, + module_dir=os.getcwd(), solution_build_config=build_config) + env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py new file mode 100644 index 0000000000..f66ffdb573 --- /dev/null +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -0,0 +1,108 @@ +# Build GodotTools solution + +import os + +from SCons.Script import Dir + + +def build_godot_tools(source, target, env): + # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str + + module_dir = env['module_dir'] + + solution_path = os.path.join(module_dir, 'editor/GodotTools/GodotTools.sln') + build_config = 'Debug' if env['target'] == 'debug' else 'Release' + + from . solution_builder import build_solution, nuget_restore + nuget_restore(env, solution_path) + build_solution(env, solution_path, build_config) + + # Copy targets + + solution_dir = os.path.abspath(os.path.join(solution_path, os.pardir)) + + src_dir = os.path.join(solution_dir, 'GodotTools', 'bin', build_config) + dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + + if not os.path.isdir(dst_dir): + assert not os.path.isfile(dst_dir) + os.makedirs(dst_dir) + + def copy_target(target_path): + from shutil import copy + filename = os.path.basename(target_path) + copy(os.path.join(src_dir, filename), target_path) + + for scons_target in target: + copy_target(str(scons_target)) + + +def build_godot_tools_project_editor(source, target, env): + # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str + + module_dir = env['module_dir'] + + project_name = 'GodotTools.ProjectEditor' + + csproj_dir = os.path.join(module_dir, 'editor/GodotTools', project_name) + csproj_path = os.path.join(csproj_dir, project_name + '.csproj') + build_config = 'Debug' if env['target'] == 'debug' else 'Release' + + from . solution_builder import build_solution, nuget_restore + + # Make sure to restore NuGet packages in the project directory for the project to find it + nuget_restore(env, os.path.join(csproj_dir, 'packages.config'), '-PackagesDirectory', + os.path.join(csproj_dir, 'packages')) + + build_solution(env, csproj_path, build_config) + + # Copy targets + + src_dir = os.path.join(csproj_dir, 'bin', build_config) + dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + + if not os.path.isdir(dst_dir): + assert not os.path.isfile(dst_dir) + os.makedirs(dst_dir) + + def copy_target(target_path): + from shutil import copy + filename = os.path.basename(target_path) + copy(os.path.join(src_dir, filename), target_path) + + for scons_target in target: + copy_target(str(scons_target)) + + +def build(env_mono): + assert env_mono['tools'] + + output_dir = Dir('#bin').abspath + editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') + editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', 'Debug') + + source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll'] + sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames] + + target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + + if env_mono['target'] == 'debug': + target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'GodotTools.Core.dll'] + + targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] + + cmd = env_mono.CommandNoCache(targets, sources, build_godot_tools, module_dir=os.getcwd()) + env_mono.AlwaysBuild(cmd) + + +def build_project_editor_only(env_mono): + assert env_mono['tools'] + + output_dir = Dir('#bin').abspath + editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') + + target_filenames = ['GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] + + cmd = env_mono.CommandNoCache(targets, [], build_godot_tools_project_editor, module_dir=os.getcwd()) + env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py new file mode 100644 index 0000000000..cd9210897d --- /dev/null +++ b/modules/mono/build_scripts/make_android_mono_config.py @@ -0,0 +1,69 @@ + +def generate_compressed_config(config_src, output_dir): + import os.path + from compat import byte_to_str + + # Header file + with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header: + header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ +#ifndef ANDROID_MONO_CONFIG_GEN_H +#define ANDROID_MONO_CONFIG_GEN_H + +#ifdef ANDROID_ENABLED + +#include "core/ustring.h" + +String get_godot_android_mono_config(); + +#endif // ANDROID_ENABLED + +#endif // ANDROID_MONO_CONFIG_GEN_H +''') + + # Source file + with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp: + with open(config_src, 'rb') as f: + buf = f.read() + decompr_size = len(buf) + import zlib + buf = zlib.compress(buf) + compr_size = len(buf) + + bytes_seq_str = '' + for i, buf_idx in enumerate(range(compr_size)): + if i > 0: + bytes_seq_str += ', ' + bytes_seq_str += byte_to_str(buf[buf_idx]) + + cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ +#include "android_mono_config.gen.h" + +#ifdef ANDROID_ENABLED + +#include "core/io/compression.h" +#include "core/pool_vector.h" + +namespace { + +// config +static const int config_compressed_size = %d; +static const int config_uncompressed_size = %d; +static const unsigned char config_compressed_data[] = { %s }; + +} // namespace + +String get_godot_android_mono_config() { + PoolVector<uint8_t> data; + data.resize(config_uncompressed_size); + PoolVector<uint8_t>::Write w = data.write(); + Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data, + config_compressed_size, Compression::MODE_DEFLATE); + String s; + if (s.parse_utf8((const char *)w.ptr(), data.size())) { + ERR_FAIL_V(String()); + } + return s; +} + +#endif // ANDROID_ENABLED +''' % (compr_size, decompr_size, bytes_seq_str)) diff --git a/modules/mono/build_scripts/make_cs_compressed_header.py b/modules/mono/build_scripts/make_cs_compressed_header.py index 1f9177cef8..ed49db5bb2 100644 --- a/modules/mono/build_scripts/make_cs_compressed_header.py +++ b/modules/mono/build_scripts/make_cs_compressed_header.py @@ -23,30 +23,31 @@ def generate_header(src, dst, version_dst): latest_mtime = mtime if mtime > latest_mtime else latest_mtime with open(filepath, 'rb') as f: buf = f.read() - decomp_size = len(buf) + decompr_size = len(buf) import zlib buf = zlib.compress(buf) + compr_size = len(buf) name = str(cs_file_count) header.write('\n') header.write('// ' + filepath_src_rel + '\n') - header.write('static const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n') - header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n') + header.write('static const int _cs_' + name + '_compressed_size = ' + str(compr_size) + ';\n') + header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decompr_size) + ';\n') header.write('static const unsigned char _cs_' + name + '_compressed[] = { ') - for i, buf_idx in enumerate(range(len(buf))): + for i, buf_idx in enumerate(range(compr_size)): if i > 0: header.write(', ') header.write(byte_to_str(buf[buf_idx])) + header.write(' };\n') inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \ - 'CompressedFile(_cs_' + name + '_compressed_size, ' \ + 'GodotCsCompressedFile(_cs_' + name + '_compressed_size, ' \ '_cs_' + name + '_uncompressed_size, ' \ '_cs_' + name + '_compressed));\n' - header.write(' };\n') - header.write('\nstruct CompressedFile\n' '{\n' + header.write('\nstruct GodotCsCompressedFile\n' '{\n' '\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n' - '\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' + '\n\tGodotCsCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' '\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n' - '\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n' - '\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n' + '\t\tdata = p_data;\n' '\t}\n' '\n\tGodotCsCompressedFile() {}\n' '};\n' + '\nvoid get_compressed_files(Map<String, GodotCsCompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n' ) header.write('\n#endif // TOOLS_ENABLED\n') header.write('\n#endif // CS_COMPRESSED_H\n') diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 160580e116..9f0eb58896 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -1,15 +1,28 @@ -import imp import os +import os.path import sys import subprocess -from distutils.version import LooseVersion -from SCons.Script import BoolVariable, Dir, Environment, Variables +from SCons.Script import Dir, Environment if os.name == 'nt': from . import mono_reg_utils as monoreg +android_arch_dirs = { + 'armv7': 'armeabi-v7a', + 'arm64v8': 'arm64-v8a', + 'x86': 'x86', + 'x86_64': 'x86_64' +} + + +def get_android_out_dir(env): + return os.path.join(Dir('#platform/android/java/libs').abspath, + 'release' if env['target'] == 'release' else 'debug', + android_arch_dirs[env['android_arch']]) + + def find_file_in_dir(directory, files, prefix='', extension=''): if not extension.startswith('.'): extension = '.' + extension @@ -20,57 +33,64 @@ def find_file_in_dir(directory, files, prefix='', extension=''): def copy_file(src_dir, dst_dir, name): - from shutil import copyfile + from shutil import copy - src_path = os.path.join(src_dir, name) - dst_path = os.path.join(dst_dir, name) + src_path = os.path.join(Dir(src_dir).abspath, name) + dst_dir = Dir(dst_dir).abspath if not os.path.isdir(dst_dir): - os.mkdir(dst_dir) + os.makedirs(dst_dir) - copyfile(src_path, dst_path) + copy(src_path, dst_dir) def configure(env, env_mono): - envvars = Variables() - envvars.Add(BoolVariable('mono_static', 'Statically link mono', False)) - envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False)) - envvars.Update(env) - bits = env['bits'] + is_android = env['platform'] == 'android' tools_enabled = env['tools'] mono_static = env['mono_static'] copy_mono_root = env['copy_mono_root'] + mono_prefix = env['mono_prefix'] + mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0'] + is_travis = os.environ.get('TRAVIS') == 'true' + + if is_travis: + # Travis CI may have a Mono version lower than 5.12 + env_mono.Append(CPPDEFINES=['NO_PENDING_EXCEPTIONS']) + + if is_android and not env['android_arch'] in android_arch_dirs: + raise RuntimeError('This module does not support for the specified \'android_arch\': ' + env['android_arch']) + + if is_android and tools_enabled: + # TODO: Implement this. We have to add the data directory to the apk, concretely the Api and Tools folders. + raise RuntimeError('This module does not currently support building for android with tools enabled') + + if is_android and mono_static: + # When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0 + raise RuntimeError('Linking Mono statically is not currently supported on Android') + + if (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix: + print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead") + if env['platform'] == 'windows': - mono_root = '' + mono_root = mono_prefix - if bits == '32': - if os.getenv('MONO32_PREFIX'): - mono_root = os.getenv('MONO32_PREFIX') - elif os.name == 'nt': - mono_root = monoreg.find_mono_root_dir(bits) - else: - if os.getenv('MONO64_PREFIX'): - mono_root = os.getenv('MONO64_PREFIX') - elif os.name == 'nt': - mono_root = monoreg.find_mono_root_dir(bits) + if not mono_root and os.name == 'nt': + mono_root = monoreg.find_mono_root_dir(bits) if not mono_root: - raise RuntimeError('Mono installation directory not found') + raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter") print('Found Mono root directory: ' + mono_root) - mono_version = mono_root_try_find_mono_version(mono_root) - configure_for_mono_version(env_mono, mono_version) - mono_lib_path = os.path.join(mono_root, 'lib') env.Append(LIBPATH=mono_lib_path) - env_mono.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) + env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) if mono_static: lib_suffix = Environment()['LIBSUFFIX'] @@ -113,21 +133,18 @@ def configure(env, env_mono): if not mono_dll_name: raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path) - copy_file(mono_bin_path, 'bin', mono_dll_name + '.dll') + copy_file(mono_bin_path, '#bin', mono_dll_name + '.dll') else: is_apple = (sys.platform == 'darwin' or "osxcross" in env) sharedlib_ext = '.dylib' if is_apple else '.so' - mono_root = '' + mono_root = mono_prefix mono_lib_path = '' + mono_so_name = '' - if bits == '32': - if os.getenv('MONO32_PREFIX'): - mono_root = os.getenv('MONO32_PREFIX') - else: - if os.getenv('MONO64_PREFIX'): - mono_root = os.getenv('MONO64_PREFIX') + if not mono_root and is_android: + raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter") if not mono_root and is_apple: # Try with some known directories under OSX @@ -142,25 +159,23 @@ def configure(env, env_mono): if not mono_root and mono_static: mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext) if not mono_root: - raise RuntimeError('Building with mono_static=yes, but failed to find the mono prefix with pkg-config. Specify one manually') + raise RuntimeError("Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " + \ + "specify one manually with the 'mono_prefix' SCons parameter") if mono_root: print('Found Mono root directory: ' + mono_root) - mono_version = mono_root_try_find_mono_version(mono_root) - configure_for_mono_version(env_mono, mono_version) - mono_lib_path = os.path.join(mono_root, 'lib') env.Append(LIBPATH=mono_lib_path) - env_mono.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) + env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) mono_lib = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension='.a') if not mono_lib: raise RuntimeError('Could not find mono library in: ' + mono_lib_path) - env_mono.Append(CPPFLAGS=['-D_REENTRANT']) + env_mono.Append(CPPDEFINES=['_REENTRANT']) if mono_static: mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a') @@ -174,6 +189,8 @@ def configure(env, env_mono): if is_apple: env.Append(LIBS=['iconv', 'pthread']) + elif is_android: + pass # Nothing else: env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) @@ -183,22 +200,16 @@ def configure(env, env_mono): if not mono_so_name: raise RuntimeError('Could not find mono shared library in: ' + mono_lib_path) - copy_file(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext) + copy_file(mono_lib_path, '#bin', 'lib' + mono_so_name + sharedlib_ext) else: assert not mono_static # TODO: Add option to force using pkg-config print('Mono root directory not found. Using pkg-config instead') - mono_version = pkgconfig_try_find_mono_version() - configure_for_mono_version(env_mono, mono_version) - env.ParseConfig('pkg-config monosgen-2 --libs') env_mono.ParseConfig('pkg-config monosgen-2 --cflags') - mono_lib_path = '' - mono_so_name = '' - tmpenv = Environment() tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH')) tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L') @@ -213,15 +224,25 @@ def configure(env, env_mono): if not mono_so_name: raise RuntimeError('Could not find mono shared library in: ' + str(tmpenv['LIBPATH'])) - copy_file(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext) + if not mono_static: + libs_output_dir = get_android_out_dir(env) if is_android else '#bin' + copy_file(mono_lib_path, libs_output_dir, 'lib' + mono_so_name + sharedlib_ext) env.Append(LINKFLAGS='-rdynamic') - if not tools_enabled: + if not tools_enabled and not is_android: if not mono_root: mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() make_template_dir(env, mono_root) + elif not tools_enabled and is_android: + # Compress Android Mono Config + from . import make_android_mono_config + config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config') + make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/') + + # Copy the required shared libraries + copy_mono_shared_libs(env, mono_root, None) if copy_mono_root: if not mono_root: @@ -241,7 +262,7 @@ def make_template_dir(env, mono_root): template_dir_name = '' - if platform in ['windows', 'osx', 'x11']: + if platform in ['windows', 'osx', 'x11', 'android']: template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target) else: assert False @@ -261,7 +282,7 @@ def make_template_dir(env, mono_root): # Copy the required shared libraries - copy_mono_shared_libs(mono_root, template_mono_root_dir, env['platform']) + copy_mono_shared_libs(env, mono_root, template_mono_root_dir) def copy_mono_root_files(env, mono_root): @@ -285,7 +306,7 @@ def copy_mono_root_files(env, mono_root): # Copy the required shared libraries - copy_mono_shared_libs(mono_root, editor_mono_root_dir, env['platform']) + copy_mono_shared_libs(env, mono_root, editor_mono_root_dir) # Copy framework assemblies @@ -332,42 +353,47 @@ def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform): copy_tree(os.path.join(mono_etc_dir, '2.0'), os.path.join(target_mono_config_dir, '2.0')) copy_tree(os.path.join(mono_etc_dir, '4.0'), os.path.join(target_mono_config_dir, '4.0')) copy_tree(os.path.join(mono_etc_dir, '4.5'), os.path.join(target_mono_config_dir, '4.5')) - copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig')) + if os.path.isdir(os.path.join(mono_etc_dir, 'mconfig')): + copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig')) for file in glob(os.path.join(mono_etc_dir, '*')): if os.path.isfile(file): copy(file, target_mono_config_dir) -def copy_mono_shared_libs(mono_root, target_mono_root_dir, platform): +def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): from shutil import copy + def copy_if_exists(src, dst): + if os.path.isfile(src): + copy(src, dst) + + platform = env['platform'] + if platform == 'windows': target_mono_bin_dir = os.path.join(target_mono_root_dir, 'bin') if not os.path.isdir(target_mono_bin_dir): os.makedirs(target_mono_bin_dir) - copy(os.path.join(mono_root, 'bin', 'MonoPosixHelper.dll'), os.path.join(target_mono_bin_dir, 'MonoPosixHelper.dll')) + copy(os.path.join(mono_root, 'bin', 'MonoPosixHelper.dll'), target_mono_bin_dir) else: - target_mono_lib_dir = os.path.join(target_mono_root_dir, 'lib') + target_mono_lib_dir = get_android_out_dir(env) if platform == 'android' else os.path.join(target_mono_root_dir, 'lib') if not os.path.isdir(target_mono_lib_dir): os.makedirs(target_mono_lib_dir) if platform == 'osx': - copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.dylib'), os.path.join(target_mono_lib_dir, 'libMonoPosixHelper.dylib')) - elif platform == 'x11': - copy(os.path.join(mono_root, 'lib', 'libmono-btls-shared.so'), os.path.join(target_mono_lib_dir, 'libmono-btls-shared.so')) - copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.so'), os.path.join(target_mono_lib_dir, 'libMonoPosixHelper.so')) - + # TODO: Make sure nothing is missing + copy(os.path.join(mono_root, 'lib', 'libMonoPosixHelper.dylib'), target_mono_lib_dir) + elif platform == 'x11' or platform == 'android': + lib_file_names = [lib_name + '.so' for lib_name in [ + 'libmono-btls-shared', 'libmono-ee-interp', 'libmono-native', 'libMonoPosixHelper', + 'libmono-profiler-aot', 'libmono-profiler-coverage', 'libmono-profiler-log', 'libMonoSupportW' + ]] -def configure_for_mono_version(env, mono_version): - if mono_version is None: - raise RuntimeError('Mono JIT compiler version not found') - print('Found Mono JIT compiler version: ' + str(mono_version)) - if mono_version >= LooseVersion('5.12.0'): - env.Append(CPPFLAGS=['-DHAS_PENDING_EXCEPTIONS']) + for lib_file_name in lib_file_names: + copy_if_exists(os.path.join(mono_root, 'lib', lib_file_name), target_mono_lib_dir) def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): @@ -379,36 +405,3 @@ def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')): return os.path.join(hint_dir, '..') return '' - - -def pkgconfig_try_find_mono_version(): - from compat import decode_utf8 - - lines = subprocess.check_output(['pkg-config', 'monosgen-2', '--modversion']).splitlines() - greater_version = None - for line in lines: - try: - version = LooseVersion(decode_utf8(line)) - if greater_version is None or version > greater_version: - greater_version = version - except ValueError: - pass - return greater_version - - -def mono_root_try_find_mono_version(mono_root): - from compat import decode_utf8 - - mono_bin = os.path.join(mono_root, 'bin') - if os.path.isfile(os.path.join(mono_bin, 'mono')): - mono_binary = os.path.join(mono_bin, 'mono') - elif os.path.isfile(os.path.join(mono_bin, 'mono.exe')): - mono_binary = os.path.join(mono_bin, 'mono.exe') - else: - return None - output = subprocess.check_output([mono_binary, '--version']) - first_line = decode_utf8(output.splitlines()[0]) - try: - return LooseVersion(first_line.split()[len('Mono JIT compiler version'.split())]) - except (ValueError, IndexError): - return None diff --git a/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff b/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff new file mode 100644 index 0000000000..05f8dcadcc --- /dev/null +++ b/modules/mono/build_scripts/patches/fix-mono-android-tkill.diff @@ -0,0 +1,70 @@ +diff --git a/libgc/include/private/gcconfig.h b/libgc/include/private/gcconfig.h +index e2bdf13ac3e..f962200ba4e 100644 +--- a/libgc/include/private/gcconfig.h ++++ b/libgc/include/private/gcconfig.h +@@ -2255,6 +2255,14 @@ + # define GETPAGESIZE() getpagesize() + # endif + ++#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \ ++ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \ ++ || defined(ARM32) || defined(I386) /* but not x32 */) ++ /* tkill() exists only on arm32/mips(32)/x86. */ ++ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */ ++# define USE_TKILL_ON_ANDROID ++#endif ++ + # if defined(SUNOS5) || defined(DRSNX) || defined(UTS4) + /* OS has SVR4 generic features. Probably others also qualify. */ + # define SVR4 +diff --git a/libgc/pthread_stop_world.c b/libgc/pthread_stop_world.c +index f93ce26b562..4a49a6d578c 100644 +--- a/libgc/pthread_stop_world.c ++++ b/libgc/pthread_stop_world.c +@@ -336,7 +336,7 @@ void GC_push_all_stacks() + pthread_t GC_stopping_thread; + int GC_stopping_pid; + +-#ifdef HOST_ANDROID ++#ifdef USE_TKILL_ON_ANDROID + static + int android_thread_kill(pid_t tid, int sig) + { +diff --git a/mono/metadata/threads.c b/mono/metadata/threads.c +index ad9b8823f8f..3542b32b540 100644 +--- a/mono/metadata/threads.c ++++ b/mono/metadata/threads.c +@@ -77,8 +77,12 @@ mono_native_thread_join_handle (HANDLE thread_handle, gboolean close_handle); + #include <zircon/syscalls.h> + #endif + +-#if defined(HOST_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64) +-#define USE_TKILL_ON_ANDROID 1 ++#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \ ++ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \ ++ || defined(ARM32) || defined(I386) /* but not x32 */) ++ /* tkill() exists only on arm32/mips(32)/x86. */ ++ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */ ++# define USE_TKILL_ON_ANDROID + #endif + + #ifdef HOST_ANDROID +diff --git a/mono/utils/mono-threads-posix.c b/mono/utils/mono-threads-posix.c +index 3e4bf93de5f..79c9f731fe7 100644 +--- a/mono/utils/mono-threads-posix.c ++++ b/mono/utils/mono-threads-posix.c +@@ -31,8 +31,12 @@ + + #include <errno.h> + +-#if defined(HOST_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64) +-#define USE_TKILL_ON_ANDROID 1 ++#if defined(HOST_ANDROID) && !(__ANDROID_API__ >= 23) \ ++ && ((defined(MIPS) && (CPP_WORDSZ == 32)) \ ++ || defined(ARM32) || defined(I386) /* but not x32 */) ++ /* tkill() exists only on arm32/mips(32)/x86. */ ++ /* NDK r11+ deprecates tkill() but keeps it for Mono clients. */ ++# define USE_TKILL_ON_ANDROID + #endif + + #ifdef USE_TKILL_ON_ANDROID diff --git a/modules/mono/build_scripts/godotsharptools_build.py b/modules/mono/build_scripts/solution_builder.py index af3a5cb5c6..9f549a10ed 100644 --- a/modules/mono/build_scripts/godotsharptools_build.py +++ b/modules/mono/build_scripts/solution_builder.py @@ -1,9 +1,8 @@ -# Build GodotSharpTools solution - import os -from SCons.Script import Builder, Dir + +verbose = False def find_nuget_unix(): @@ -53,21 +52,9 @@ def find_nuget_windows(env): if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path - from . import mono_reg_utils as monoreg + from . mono_reg_utils import find_mono_root_dir - mono_root = '' - bits = env['bits'] - - if bits == '32': - if os.getenv('MONO32_PREFIX'): - mono_root = os.getenv('MONO32_PREFIX') - else: - mono_root = monoreg.find_mono_root_dir(bits) - else: - if os.getenv('MONO64_PREFIX'): - mono_root = os.getenv('MONO64_PREFIX') - else: - mono_root = monoreg.find_mono_root_dir(bits) + mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits']) if mono_root: mono_bin_dir = os.path.join(mono_root, 'bin') @@ -114,21 +101,9 @@ def find_msbuild_unix(filename): def find_msbuild_windows(env): - from . import mono_reg_utils as monoreg + from . mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg - mono_root = '' - bits = env['bits'] - - if bits == '32': - if os.getenv('MONO32_PREFIX'): - mono_root = os.getenv('MONO32_PREFIX') - else: - mono_root = monoreg.find_mono_root_dir(bits) - else: - if os.getenv('MONO64_PREFIX'): - mono_root = os.getenv('MONO64_PREFIX') - else: - mono_root = monoreg.find_mono_root_dir(bits) + mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits']) if not mono_root: raise RuntimeError('Cannot find mono root directory') @@ -148,7 +123,7 @@ def find_msbuild_windows(env): } return (msbuild_mono, framework_path, mono_msbuild_env) - msbuild_tools_path = monoreg.find_msbuild_tools_path_reg() + msbuild_tools_path = find_msbuild_tools_path_reg() if msbuild_tools_path: return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), framework_path, {}) @@ -156,12 +131,46 @@ def find_msbuild_windows(env): return None -def mono_build_solution(source, target, env): +def run_command(command, args, env_override=None, name=None): + def cmd_args_to_str(cmd_args): + return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args]) + + args = [command] + args + + if name is None: + name = os.path.basename(command) + + if verbose: + print("Running '%s': %s" % (name, cmd_args_to_str(args))) + import subprocess - from shutil import copyfile + try: + if env_override is None: + subprocess.check_call(args) + else: + subprocess.check_call(args, env=env_override) + except subprocess.CalledProcessError as e: + raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode)) + + +def nuget_restore(env, *args): + global verbose + verbose = env['verbose'] + + # Find NuGet + nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix() + if nuget_path is None: + raise RuntimeError('Cannot find NuGet executable') + + print('NuGet path: ' + nuget_path) + + # Do NuGet restore + run_command(nuget_path, ['restore'] + list(args), name='nuget restore') - sln_path = os.path.abspath(str(source[0])) - target_path = os.path.abspath(str(target[0])) + +def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): + global verbose + verbose = env['verbose'] framework_path = '' msbuild_env = os.environ.copy() @@ -200,64 +209,10 @@ def mono_build_solution(source, target, env): print('MSBuild path: ' + msbuild_path) - # Find NuGet - nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix() - if nuget_path is None: - raise RuntimeError('Cannot find NuGet executable') - - print('NuGet path: ' + nuget_path) - - # Do NuGet restore - - try: - subprocess.check_call([nuget_path, 'restore', sln_path]) - except subprocess.CalledProcessError: - raise RuntimeError('GodotSharpTools: NuGet restore failed') - # Build solution - build_config = 'Release' - - msbuild_args = [ - msbuild_path, - sln_path, - '/p:Configuration=' + build_config, - ] - - if framework_path: - msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] - - try: - subprocess.check_call(msbuild_args, env=msbuild_env) - except subprocess.CalledProcessError: - raise RuntimeError('GodotSharpTools: Build failed') - - # Copy files - - src_dir = os.path.abspath(os.path.join(sln_path, os.pardir, 'bin', build_config)) - dst_dir = os.path.abspath(os.path.join(target_path, os.pardir)) - asm_file = 'GodotSharpTools.dll' - - if not os.path.isdir(dst_dir): - if os.path.exists(dst_dir): - raise RuntimeError('Target directory is a file') - os.makedirs(dst_dir) - - copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file)) - - # Dependencies - copyfile(os.path.join(src_dir, "DotNet.Glob.dll"), os.path.join(dst_dir, "DotNet.Glob.dll")) - -def build(env_mono): - if not env_mono['tools']: - return - - output_dir = Dir('#bin').abspath - editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') + msbuild_args = [solution_path, '/p:Configuration=' + build_config] + msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] if framework_path else [] + msbuild_args += extra_msbuild_args - mono_sln_builder = Builder(action=mono_build_solution) - env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder}) - env_mono.MonoBuildSolution( - os.path.join(editor_tools_dir, 'GodotSharpTools.dll'), - 'editor/GodotSharpTools/GodotSharpTools.sln' - ) + run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild') diff --git a/modules/mono/config.py b/modules/mono/config.py index 3b2e96765e..9adf4ee6e5 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -8,6 +8,16 @@ def configure(env): env.use_ptrcall = True env.add_module_version_string('mono') + from SCons.Script import BoolVariable, PathVariable, Variables + + envvars = Variables() + envvars.Add(PathVariable('mono_prefix', 'Path to the mono installation directory for the target platform and architecture', '', PathVariable.PathAccept)) + envvars.Add(BoolVariable('mono_static', 'Statically link mono', False)) + envvars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True)) + envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False)) + envvars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False)) + envvars.Update(env) + def get_doc_classes(): return [ diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 3c9644127c..7492816f18 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -42,9 +42,9 @@ #include "editor/bindings_generator.h" #include "editor/csharp_project.h" #include "editor/editor_node.h" -#include "editor/godotsharp_editor.h" #endif +#include "editor/editor_internal_calls.h" #include "godotsharp_dirs.h" #include "mono_gd/gd_mono_class.h" #include "mono_gd/gd_mono_marshal.h" @@ -65,8 +65,8 @@ static bool _create_project_solution_if_needed() { if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) { // A solution does not yet exist, create a new one - CRASH_COND(GodotSharpEditor::get_singleton() == NULL); - return GodotSharpEditor::get_singleton()->call("_create_project_solution"); + CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL); + return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution"); } return true; @@ -96,14 +96,6 @@ Error CSharpLanguage::execute_file(const String &p_path) { return OK; } -#ifdef TOOLS_ENABLED -void gdsharp_editor_init_callback() { - - EditorNode *editor = EditorNode::get_singleton(); - editor->add_child(memnew(GodotSharpEditor(editor))); -} -#endif - void CSharpLanguage::init() { gdmono = memnew(GDMono); @@ -114,14 +106,12 @@ void CSharpLanguage::init() { #endif #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) - if (gdmono->get_editor_tools_assembly() != NULL) { - List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); - BindingsGenerator::handle_cmdline_args(cmdline_args); - } + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + BindingsGenerator::handle_cmdline_args(cmdline_args); #endif #ifdef TOOLS_ENABLED - EditorNode::add_init_callback(&gdsharp_editor_init_callback); + EditorNode::add_init_callback(&_editor_init_callback); GLOBAL_DEF("mono/export/include_scripts_content", false); #endif @@ -131,14 +121,6 @@ void CSharpLanguage::finish() { finalizing = true; -#ifdef TOOLS_ENABLED - // Must be here, to avoid StringName leaks - if (BindingsGenerator::singleton) { - memdelete(BindingsGenerator::singleton); - BindingsGenerator::singleton = NULL; - } -#endif - // Make sure all script binding gchandles are released before finalizing GDMono for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); @@ -672,7 +654,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft CRASH_COND(!Engine::get_singleton()->is_editor_hint()); #ifdef TOOLS_ENABLED - MonoReloadNode::get_singleton()->restart_reload_timer(); + get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer"); #endif #ifdef GD_MONO_HOT_RELOAD @@ -690,19 +672,20 @@ bool CSharpLanguage::is_assembly_reloading_needed() { GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } - name += ".dll"; + appname_safe += ".dll"; if (proj_assembly) { String proj_asm_path = proj_assembly->get_path(); if (!FileAccess::exists(proj_assembly->get_path())) { // Maybe it wasn't loaded from the default path, so check this as well - proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name); + proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe); if (!FileAccess::exists(proj_asm_path)) return false; // No assembly to load } @@ -710,7 +693,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) return false; // Already up to date } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name))) + if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) return false; // No assembly to load } @@ -738,58 +721,93 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { SCOPED_MUTEX_LOCK(script_instances_mutex); for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) { - if (elem->self()->get_path().is_resource_file()) { - // Cast to CSharpScript to avoid being erased by accident - scripts.push_back(Ref<CSharpScript>(elem->self())); - } + // Cast to CSharpScript to avoid being erased by accident + scripts.push_back(Ref<CSharpScript>(elem->self())); } } List<Ref<CSharpScript> > to_reload; + // We need to keep reference instances alive during reloading + List<Ref<Reference> > ref_instances; + + for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { + CSharpScriptBinding &script_binding = E->value(); + Reference *ref = Object::cast_to<Reference>(script_binding.owner); + if (ref) { + ref_instances.push_back(Ref<Reference>(ref)); + } + } + // As scripts are going to be reloaded, must proceed without locking here scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { - Ref<CSharpScript> &script = E->get(); to_reload.push_back(script); + if (script->get_path().empty()) { + script->tied_class_name_for_reload = script->script_class->get_name(); + script->tied_class_namespace_for_reload = script->script_class->get_namespace(); + } + // Script::instances are deleted during managed object disposal, which happens on domain finalize. // Only placeholders are kept. Therefore we need to keep a copy before that happens. for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) { - script->pending_reload_instances.insert(F->get()->get_instance_id()); + Object *obj = F->get(); + script->pending_reload_instances.insert(obj->get_instance_id()); + + Reference *ref = Object::cast_to<Reference>(obj); + if (ref) { + ref_instances.push_back(Ref<Reference>(ref)); + } } #ifdef TOOLS_ENABLED for (Set<PlaceHolderScriptInstance *>::Element *F = script->placeholders.front(); F; F = F->next()) { - script->pending_reload_instances.insert(F->get()->get_owner()->get_instance_id()); + Object *obj = F->get()->get_owner(); + script->pending_reload_instances.insert(obj->get_instance_id()); + + Reference *ref = Object::cast_to<Reference>(obj); + if (ref) { + ref_instances.push_back(Ref<Reference>(ref)); + } } #endif - // FIXME: What about references? Need to keep them alive if only managed code references them. - // Save state and remove script from instances Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state; - while (script->instances.front()) { - Object *obj = script->instances.front()->get(); - // Save instance info - CSharpScript::StateBackup state; + for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) { + Object *obj = F->get(); ERR_CONTINUE(!obj->get_script_instance()); - // TODO: Proper state backup (Not only variants, serialize managed state of scripts) - obj->get_script_instance()->get_property_state(state.properties); + CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); - Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle; - if (gchandle.is_valid()) - gchandle->release(); + // Call OnBeforeSerialize + if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) + obj->get_script_instance()->call_multilevel(string_names.on_before_serialize); + + // Save instance info + CSharpScript::StateBackup state; + + // TODO: Proper state backup (Not only variants, serialize managed state of scripts) + csi->get_properties_state_for_reloading(state.properties); owners_map[obj->get_instance_id()] = state; + } + } + + // After the state of all instances is saved, clear scripts and script instances + for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + Ref<CSharpScript> &script = E->get(); + + while (script->instances.front()) { + Object *obj = script->instances.front()->get(); obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload) } @@ -832,26 +850,80 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->pending_reload_state.erase(obj_id); } } + return; } + List<Ref<CSharpScript> > to_reload_state; + for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) { + Ref<CSharpScript> script = E->get(); - Ref<CSharpScript> scr = E->get(); + if (!script->get_path().empty()) { #ifdef TOOLS_ENABLED - scr->exports_invalidated = true; + script->exports_invalidated = true; #endif - scr->signals_invalidated = true; - scr->reload(p_soft_reload); - scr->update_exports(); + script->signals_invalidated = true; + + script->reload(p_soft_reload); + script->update_exports(); + + if (!script->valid) { + script->pending_reload_instances.clear(); + continue; + } + } else { + const StringName &class_namespace = script->tied_class_namespace_for_reload; + const StringName &class_name = script->tied_class_name_for_reload; + GDMonoAssembly *project_assembly = gdmono->get_project_assembly(); + + // Search in project and tools assemblies first as those are the most likely to have the class + GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL); + +#ifdef TOOLS_ENABLED + if (!script_class) { + GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly(); + script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL); + } +#endif + + if (!script_class) { + script_class = gdmono->get_class(class_namespace, class_name); + } + + if (!script_class) { + // The class was removed, can't reload + script->pending_reload_instances.clear(); + continue; + } + + bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class); + if (!obj_type) { + // The class no longer inherits Godot.Object, can't reload + script->pending_reload_instances.clear(); + continue; + } + + GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class); + + CSharpScript::initialize_for_managed_type(script, script_class, native); + } + + String native_name = NATIVE_GDMONOCLASS_NAME(script->native); { - for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) { + for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { ObjectID obj_id = F->get(); Object *obj = ObjectDB::get_instance(obj_id); if (!obj) { - scr->pending_reload_state.erase(obj_id); + script->pending_reload_state.erase(obj_id); + continue; + } + + if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) { + // No longer inherits the same compatible type, can't reload + script->pending_reload_state.erase(obj_id); continue; } @@ -863,28 +935,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // Non-placeholder script instances are removed in godot_icall_Object_Disposed. CRASH_COND(!si->is_placeholder()); - if (scr->is_tool() || ScriptServer::is_scripting_enabled()) { + if (script->is_tool() || ScriptServer::is_scripting_enabled()) { // Replace placeholder with a script instance - CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id]; + CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; // Backup placeholder script instance state before replacing it with a script instance si->get_property_state(state_backup.properties); - ScriptInstance *script_instance = scr->instance_create(obj); + ScriptInstance *script_instance = script->instance_create(obj); if (script_instance) { - scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); + script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); obj->set_script_instance(script_instance); } - - // TODO: Restore serialized state - - for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) { - script_instance->set(G->get().first, G->get().second); - } - - scr->pending_reload_state.erase(obj_id); } continue; @@ -893,20 +957,42 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CRASH_COND(si != NULL); #endif // Re-create script instance + obj->set_script(script.get_ref_ptr()); // will create the script instance as well + } + } - obj->set_script(scr.get_ref_ptr()); // will create the script instance as well + to_reload_state.push_back(script); + } - // TODO: Restore serialized state + for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) { + Ref<CSharpScript> script = E->get(); - for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) { - obj->get_script_instance()->set(G->get().first, G->get().second); - } + for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { + ObjectID obj_id = F->get(); + Object *obj = ObjectDB::get_instance(obj_id); - scr->pending_reload_state.erase(obj_id); + if (!obj) { + script->pending_reload_state.erase(obj_id); + continue; } - scr->pending_reload_instances.clear(); + ERR_CONTINUE(!obj->get_script_instance()); + + // TODO: Restore serialized state + + CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; + + for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) { + obj->get_script_instance()->set(G->get().first, G->get().second); + } + + // Call OnAfterDeserialization + CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); + if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) + obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize); } + + script->pending_reload_instances.clear(); } #ifdef TOOLS_ENABLED @@ -919,7 +1005,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #endif -void CSharpLanguage::project_assembly_loaded() { +void CSharpLanguage::_load_scripts_metadata() { scripts_metadata.clear(); @@ -953,6 +1039,7 @@ void CSharpLanguage::project_assembly_loaded() { } scripts_metadata = old_dict_var.operator Dictionary(); + scripts_metadata_invalidated = false; print_verbose("Successfully loaded scripts metadata"); } else { @@ -970,12 +1057,12 @@ void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const #ifdef TOOLS_ENABLED Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { - return GodotSharpEditor::get_singleton()->open_in_external_editor(p_script, p_line, p_col); + return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col); } bool CSharpLanguage::overrides_external_editor() { - return GodotSharpEditor::get_singleton()->overrides_external_editor(); + return get_godotsharp_editor()->call("OverridesExternalEditor"); } #endif @@ -1024,12 +1111,42 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { } } -void CSharpLanguage::_uninitialize_script_bindings() { +void CSharpLanguage::_on_scripts_domain_unloaded() { for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); script_binding.inited = false; } + + scripts_metadata_invalidated = true; +} + +#ifdef TOOLS_ENABLED +void CSharpLanguage::_editor_init_callback() { + + register_editor_internal_calls(); + + // Initialize GodotSharpEditor + + GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor"); + CRASH_COND(editor_klass == NULL); + + MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr()); + CRASH_COND(mono_object == NULL); + + MonoException *exc = NULL; + GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc); + UNHANDLED_EXCEPTION(exc); + + EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object)); + CRASH_COND(godotsharp_editor == NULL); + + // Enable it as a plugin + EditorNode::add_editor_plugin(godotsharp_editor); + godotsharp_editor->enable_plugin(); + + get_singleton()->godotsharp_editor = godotsharp_editor; } +#endif void CSharpLanguage::set_language_index(int p_idx) { @@ -1086,6 +1203,12 @@ CSharpLanguage::CSharpLanguage() { #endif lang_idx = -1; + + scripts_metadata_invalidated = true; + +#ifdef TOOLS_ENABLED + godotsharp_editor = NULL; +#endif } CSharpLanguage::~CSharpLanguage() { @@ -1141,6 +1264,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b r_script_binding.type_name = type_name; r_script_binding.wrapper_class = type_class; // cache r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object); + r_script_binding.owner = p_object; // Tie managed to unmanaged Reference *ref = Object::cast_to<Reference>(p_object); @@ -1225,6 +1349,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + if (!script_binding.inited) + return; + if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, @@ -1249,14 +1376,17 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { CRASH_COND(!ref_owner); #endif - int refcount = ref_owner->reference_get_count(); - void *data = p_object->get_script_instance_binding(get_language_index()); CRASH_COND(!data); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + int refcount = ref_owner->reference_get_count(); + + if (!script_binding.inited) + return refcount == 0; + if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. @@ -1419,6 +1549,31 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { return false; } +void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) { + + List<PropertyInfo> pinfo; + get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + Pair<StringName, Variant> state_pair; + state_pair.first = E->get().name; + + ManagedType managedType; + + GDMonoField *field = script->script_class->get_field(state_pair.first); + if (!field) + continue; // Properties ignored. We get the property baking fields instead. + + managedType = field->get_type(); + + if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it + if (get(state_pair.first, state_pair.second)) { + r_state.push_back(state_pair); + } + } + } +} + void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) { @@ -1616,17 +1771,18 @@ MonoObject *CSharpInstance::_internal_new_managed() { ERR_FAIL_NULL_V(owner, NULL); ERR_FAIL_COND_V(script.is_null(), NULL); - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr()); if (!mono_object) { // Important to clear this before destroying the script instance here script = Ref<CSharpScript>(); - owner = NULL; bool die = _unreference_owner_unsafe(); // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. CRASH_COND(die == true); + owner = NULL; + ERR_EXPLAIN("Failed to allocate memory for the object"); ERR_FAIL_V(NULL); } @@ -1858,6 +2014,34 @@ void CSharpInstance::_call_notification(int p_notification) { } } +String CSharpInstance::to_string(bool *r_valid) { + MonoObject *mono_object = get_mono_object(); + + if (mono_object == NULL) { + if (r_valid) + *r_valid = false; + return String(); + } + + MonoException *exc = NULL; + MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); + if (r_valid) + *r_valid = false; + return String(); + } + + if (result == NULL) { + if (r_valid) + *r_valid = false; + return String(); + } + + return GDMonoMarshal::mono_string_to_godot(result); +} + Ref<Script> CSharpInstance::get_script() const { return script; @@ -1914,7 +2098,16 @@ CSharpInstance::~CSharpInstance() { CRASH_COND(data == NULL); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - CRASH_COND(!script_binding.inited); + + if (!script_binding.inited) { + SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex()); + + if (!script_binding.inited) { // Other thread may have set it up + // Already had a binding that needs to be setup + CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, owner); + CRASH_COND(!script_binding.inited); + } + } bool die = _unreference_owner_unsafe(); CRASH_COND(die == true); // The "instance binding" should be holding a reference @@ -1956,6 +2149,52 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List propnames.push_back(E->get()); } } + +void CSharpScript::_update_member_info_no_exports() { + + if (exports_invalidated) { + exports_invalidated = false; + + member_info.clear(); + + GDMonoClass *top = script_class; + + while (top && top != native) { + PropertyInfo prop_info; + bool exported; + + const Vector<GDMonoField *> &fields = top->get_all_fields(); + + for (int i = fields.size() - 1; i >= 0; i--) { + GDMonoField *field = fields[i]; + + if (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) { + StringName member_name = field->get_name(); + + member_info[member_name] = prop_info; + exported_members_cache.push_front(prop_info); + exported_members_defval_cache[member_name] = Variant(); + } + } + + const Vector<GDMonoProperty *> &properties = top->get_all_properties(); + + for (int i = properties.size() - 1; i >= 0; i--) { + GDMonoProperty *property = properties[i]; + + if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) { + StringName member_name = property->get_name(); + + member_info[member_name] = prop_info; + exported_members_cache.push_front(prop_info); + exported_members_defval_cache[member_name] = Variant(); + } + } + + top = top->get_parent_class(); + } + } +} #endif bool CSharpScript::_update_exports() { @@ -1982,7 +2221,7 @@ bool CSharpScript::_update_exports() { // Here we create a temporary managed instance of the class to get the initial values - MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr()); + MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); if (!tmp_object) { ERR_PRINT("Failed to allocate temporary MonoObject"); @@ -2023,18 +2262,18 @@ bool CSharpScript::_update_exports() { for (int i = fields.size() - 1; i >= 0; i--) { GDMonoField *field = fields[i]; - if (_get_member_export(top, field, prop_info, exported)) { - StringName name = field->get_name(); + if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) { + StringName member_name = field->get_name(); if (exported) { - member_info[name] = prop_info; + member_info[member_name] = prop_info; exported_members_cache.push_front(prop_info); if (tmp_object) { - exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); + exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); } } else { - member_info[name] = prop_info; + member_info[member_name] = prop_info; } } } @@ -2044,25 +2283,25 @@ bool CSharpScript::_update_exports() { for (int i = properties.size() - 1; i >= 0; i--) { GDMonoProperty *property = properties[i]; - if (_get_member_export(top, property, prop_info, exported)) { - StringName name = property->get_name(); + if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) { + StringName member_name = property->get_name(); if (exported) { - member_info[name] = prop_info; + member_info[member_name] = prop_info; exported_members_cache.push_front(prop_info); if (tmp_object) { MonoException *exc = NULL; MonoObject *ret = property->get_value(tmp_object, &exc); if (exc) { - exported_members_defval_cache[name] = Variant(); + exported_members_defval_cache[member_name] = Variant(); GDMonoUtils::debug_print_unhandled_exception(exc); } else { - exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret); + exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); } } } else { - member_info[name] = prop_info; + member_info[member_name] = prop_info; } } } @@ -2171,17 +2410,19 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve * Returns false if there was an error, otherwise true. * If there was an error, r_prop_info and r_exported are not assigned any value. */ -bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) { +bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) { - StringName name = p_member->get_name(); + // Goddammit, C++. All I wanted was some nested functions. +#define MEMBER_FULL_QUALIFIED_NAME(m_member) \ + (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) if (p_member->is_static()) { if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) - ERR_PRINTS("Cannot export member because it is static: " + p_class->get_full_name() + "." + name.operator String()); + ERR_PRINTS("Cannot export member because it is static: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); return false; } - if (member_info.has(name)) + if (member_info.has(p_member->get_name())) return false; ManagedType type; @@ -2194,39 +2435,68 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_ CRASH_NOW(); } - GDMonoMarshal::ExportInfo export_info; - Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type, &export_info); - - if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) { - r_prop_info = PropertyInfo(variant_type, name.operator String(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); - r_exported = false; - return true; - } + bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute)); if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); - if (!property->has_getter() || !property->has_setter()) { - ERR_PRINTS("Cannot export property because it does not provide a getter or a setter: " + p_class->get_full_name() + "." + name.operator String()); + if (!property->has_getter()) { + if (exported) + ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + return false; + } + if (!property->has_setter()) { + if (exported) + ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); return false; } } + Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type); + + if (!p_inspect_export || !exported) { + r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); + r_exported = false; + return true; + } + MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); PropertyHint hint = PROPERTY_HINT_NONE; String hint_string; if (variant_type == Variant::NIL) { - ERR_PRINTS("Unknown type of exported member: " + p_class->get_full_name() + "." + name.operator String()); + ERR_PRINTS("Unknown exported member type: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); return false; - } else if (variant_type == Variant::INT && type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(type.type_class->get_mono_ptr())) { - // TODO: Move to ExportInfo? - variant_type = Variant::INT; - hint = PROPERTY_HINT_ENUM; + } + + int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); + + if (hint_res == -1) { + ERR_EXPLAIN("Error while trying to determine information about the exported member: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_FAIL_V(false); + } + + if (hint_res == 0) { + hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); + hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); + } + + r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE); + r_exported = true; + + return true; + +#undef MEMBER_FULL_QUALIFIED_NAME +} - Vector<MonoClassField *> fields = type.type_class->get_enum_fields(); +int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { - MonoType *enum_basetype = mono_class_enum_basetype(type.type_class->get_mono_ptr()); + if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) { + r_hint = PROPERTY_HINT_ENUM; + + Vector<MonoClassField *> fields = p_type.type_class->get_enum_fields(); + + MonoType *enum_basetype = mono_class_enum_basetype(p_type.type_class->get_mono_ptr()); String name_only_hint_string; @@ -2239,12 +2509,12 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_ MonoClassField *field = fields[i]; if (i > 0) { - hint_string += ","; + r_hint_string += ","; name_only_hint_string += ","; } String enum_field_name = mono_field_get_name(field); - hint_string += enum_field_name; + r_hint_string += enum_field_name; name_only_hint_string += enum_field_name; // TODO: @@ -2254,53 +2524,73 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_ MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL); if (val_obj == NULL) { - ERR_PRINTS("Failed to get '" + enum_field_name + "' constant enum value of exported member: " + - p_class->get_full_name() + "." + name.operator String()); - return false; + ERR_EXPLAIN("Failed to get '" + enum_field_name + "' constant enum value"); + ERR_FAIL_V(-1); } bool r_error; uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error); if (r_error) { - ERR_PRINTS("Failed to unbox '" + enum_field_name + "' constant enum value of exported member: " + - p_class->get_full_name() + "." + name.operator String()); - return false; + ERR_EXPLAIN("Failed to unbox '" + enum_field_name + "' constant enum value"); + ERR_FAIL_V(-1); } if (val != (unsigned int)i) { uses_default_values = false; } - hint_string += ":"; - hint_string += String::num_uint64(val); + r_hint_string += ":"; + r_hint_string += String::num_uint64(val); } if (uses_default_values) { // If we use the format NAME:VAL, that's what the editor displays. // That's annoying if the user is not using custom values for the enum constants. // This may not be needed in the future if the editor is changed to not display values. - hint_string = name_only_hint_string; + r_hint_string = name_only_hint_string; } - } else if (variant_type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(type.type_class)) { - GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(type.type_class); + } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) { + GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class); CRASH_COND(field_native_class == NULL); - hint = PROPERTY_HINT_RESOURCE_TYPE; - hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class); - } else if (variant_type == Variant::ARRAY && export_info.array.element_type != Variant::NIL) { - hint = PROPERTY_HINT_TYPE_STRING; - hint_string = itos(export_info.array.element_type) + ":"; - } else if (variant_type == Variant::DICTIONARY && export_info.dictionary.key_type != Variant::NIL && export_info.dictionary.value_type != Variant::NIL) { - // TODO: There is no hint for this yet + r_hint = PROPERTY_HINT_RESOURCE_TYPE; + r_hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class); + } else if (p_allow_generics && p_variant_type == Variant::ARRAY) { + // Nested arrays are not supported in the inspector + + ManagedType elem_type; + + if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type)) + return 0; + + Variant::Type elem_variant_type = GDMonoMarshal::managed_to_variant_type(elem_type); + + PropertyHint elem_hint = PROPERTY_HINT_NONE; + String elem_hint_string; + + if (elem_variant_type == Variant::NIL) { + ERR_EXPLAIN("Unknown array element type"); + ERR_FAIL_V(-1); + } + + int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string); + + if (hint_res == -1) { + ERR_EXPLAIN("Error while trying to determine information about the array element type"); + ERR_FAIL_V(-1); + } + + // Format: type/hint:hint_string + r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string; + r_hint = PROPERTY_HINT_TYPE_STRING; + + } else if (p_allow_generics && p_variant_type == Variant::DICTIONARY) { + // TODO: Dictionaries are not supported in the inspector } else { - hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); - hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); + return 0; } - r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE); - r_exported = true; - - return true; + return 1; } #endif @@ -2388,33 +2678,58 @@ void CSharpScript::_bind_methods() { Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail + // This method should not fail, only assertions allowed - CRASH_COND(!p_class); + CRASH_COND(p_class == NULL); - // TODO: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time + // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time Ref<CSharpScript> script = memnew(CSharpScript); - script->name = p_class->get_name(); - script->script_class = p_class; - script->native = p_native; + initialize_for_managed_type(script, p_class, p_native); + + return script; +} + +void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) { - CRASH_COND(script->native == NULL); + // This method should not fail, only assertions allowed - GDMonoClass *base = script->script_class->get_parent_class(); + CRASH_COND(p_class == NULL); - if (base != script->native) - script->base = base; + p_script->name = p_class->get_name(); + p_script->script_class = p_class; + p_script->native = p_native; + + CRASH_COND(p_script->native == NULL); + + GDMonoClass *base = p_script->script_class->get_parent_class(); + + if (base != p_script->native) + p_script->base = base; + + p_script->valid = true; + p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + + if (!p_script->tool) { + GDMonoClass *nesting_class = p_script->script_class->get_nesting_class(); + p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); + } + +#if TOOLS_ENABLED + if (!p_script->tool) { + p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); + } +#endif #ifdef DEBUG_ENABLED // For debug builds, we must fetch from all native base methods as well. // Native base methods must be fetched before the current class. // Not needed if the script class itself is a native class. - if (script->script_class != script->native) { - GDMonoClass *native_top = script->native; + if (p_script->script_class != p_script->native) { + GDMonoClass *native_top = p_script->native; while (native_top) { - native_top->fetch_methods_with_godot_api_checks(script->native); + native_top->fetch_methods_with_godot_api_checks(p_script->native); if (native_top == CACHED_CLASS(GodotObject)) break; @@ -2424,18 +2739,19 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD } #endif - script->script_class->fetch_methods_with_godot_api_checks(script->native); + p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native); // Need to fetch method from base classes as well - GDMonoClass *top = script->script_class; - while (top && top != script->native) { - top->fetch_methods_with_godot_api_checks(script->native); + GDMonoClass *top = p_script->script_class; + while (top && top != p_script->native) { + top->fetch_methods_with_godot_api_checks(p_script->native); top = top->get_parent_class(); } - script->load_script_signals(script->script_class, script->native); - - return script; + p_script->load_script_signals(p_script->script_class, p_script->native); +#ifdef TOOLS_ENABLED + p_script->_update_member_info_no_exports(); +#endif } bool CSharpScript::can_instance() const { @@ -2443,7 +2759,8 @@ bool CSharpScript::can_instance() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - if (get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted... + // Hack to lower the risk of attached scripts not being added to the C# project + if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted... if (_create_project_solution_if_needed()) { CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(), "Compile", @@ -2495,7 +2812,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); if (ctor == NULL) { if (p_argcount == 0) { - ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + get_path()); + String path = get_path(); + ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() + + "' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path)); } ERR_EXPLAIN("Constructor not found"); @@ -2537,7 +2856,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg /* STEP 2, INITIALIZE AND CONSTRUCT */ - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); if (!mono_object) { // Important to clear this before destroying the script instance here @@ -2618,7 +2937,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { #endif if (native) { - String native_name = native->get_name(); + String native_name = NATIVE_GDMONOCLASS_NAME(native); if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { if (ScriptDebugger::get_singleton()) { CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); @@ -2629,7 +2948,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { } Variant::CallError unchecked_error; - return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error); + return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error); } PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) { @@ -2744,11 +3063,22 @@ Error CSharpScript::reload(bool p_keep_state) { if (script_class) { #ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path()); + print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); #endif tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + if (!tool) { + GDMonoClass *nesting_class = script_class->get_nesting_class(); + tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); + } + +#if TOOLS_ENABLED + if (!tool) { + tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); + } +#endif + native = GDMonoUtils::get_class_native_base(script_class); CRASH_COND(native == NULL); @@ -2826,10 +3156,7 @@ void CSharpScript::update_exports() { } bool CSharpScript::has_script_signal(const StringName &p_signal) const { - if (_signals.has(p_signal)) - return true; - - return false; + return _signals.has(p_signal); } void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { @@ -2949,7 +3276,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p #endif #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint() && mono_domain_get() == NULL) { + MonoDomain *domain = mono_domain_get(); + if (Engine::get_singleton()->is_editor_hint() && domain == NULL) { CRASH_COND(Thread::get_caller_id() == Thread::get_main_id()); @@ -2957,8 +3285,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p // because this may be called by one of the editor's worker threads. // Attach this thread temporarily to reload the script. - if (SCRIPTS_DOMAIN) { - MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN); + if (domain) { + MonoThread *mono_thread = mono_thread_attach(domain); CRASH_COND(mono_thread == NULL); script->reload(); mono_thread_detach(mono_thread); @@ -3058,5 +3386,7 @@ CSharpLanguage::StringNameCache::StringNameCache() { _get_property_list = StaticCString::create("_get_property_list"); _notification = StaticCString::create("_notification"); _script_source = StaticCString::create("script/source"); + on_before_serialize = StaticCString::create("OnBeforeSerialize"); + on_after_deserialize = StaticCString::create("OnAfterDeserialize"); dotctor = StaticCString::create(".ctor"); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 050527d52b..eb168f344d 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -41,6 +41,10 @@ #include "mono_gd/gd_mono_header.h" #include "mono_gd/gd_mono_internals.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_plugin.h" +#endif + class CSharpScript; class CSharpInstance; class CSharpLanguage; @@ -63,7 +67,7 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { class CSharpScript : public Script { - GDCLASS(CSharpScript, Script) + GDCLASS(CSharpScript, Script); friend class CSharpInstance; friend class CSharpLanguage; @@ -92,6 +96,8 @@ class CSharpScript : public Script { Set<ObjectID> pending_reload_instances; Map<ObjectID, StateBackup> pending_reload_state; + StringName tied_class_name_for_reload; + StringName tied_class_namespace_for_reload; #endif String source; @@ -115,6 +121,7 @@ class CSharpScript : public Script { bool placeholder_fallback_enabled; bool exports_invalidated; void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames); + void _update_member_info_no_exports(); virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); #endif @@ -127,7 +134,8 @@ class CSharpScript : public Script { bool _update_exports(); #ifdef TOOLS_ENABLED - bool _get_member_export(GDMonoClass *p_class, IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported); + bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported); + static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string); #endif CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error); @@ -136,6 +144,7 @@ class CSharpScript : public Script { // Do not use unless you know what you are doing friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native); + static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native); protected: static void _bind_methods(); @@ -225,6 +234,8 @@ class CSharpInstance : public ScriptInstance { MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const; + void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state); + public: MonoObject *get_mono_object() const; @@ -260,6 +271,8 @@ public: virtual void notification(int p_notification); void _call_notification(int p_notification); + virtual String to_string(bool *r_valid); + virtual Ref<Script> get_script() const; virtual ScriptLanguage *get_language(); @@ -273,6 +286,7 @@ struct CSharpScriptBinding { StringName type_name; GDMonoClass *wrapper_class; Ref<MonoGCHandle> gchandle; + Object *owner; }; class CSharpLanguage : public ScriptLanguage { @@ -302,6 +316,8 @@ class CSharpLanguage : public ScriptLanguage { StringName _notification; StringName _script_source; StringName dotctor; // .ctor + StringName on_before_serialize; // OnBeforeSerialize + StringName on_after_deserialize; // OnAfterDeserialize StringNameCache(); }; @@ -309,14 +325,23 @@ class CSharpLanguage : public ScriptLanguage { int lang_idx; Dictionary scripts_metadata; + bool scripts_metadata_invalidated; // For debug_break and debug_break_parse int _debug_parse_err_line; String _debug_parse_err_file; String _debug_error; + void _load_scripts_metadata(); + friend class GDMono; - void _uninitialize_script_bindings(); + void _on_scripts_domain_unloaded(); + +#ifdef TOOLS_ENABLED + EditorPlugin *godotsharp_editor; + + static void _editor_init_callback(); +#endif public: StringNameCache string_names; @@ -330,8 +355,12 @@ public: _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; } +#ifdef TOOLS_ENABLED + _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; } +#endif + static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle); - static void release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle); + static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle); bool debug_break(const String &p_error, bool p_allow_continue = true); bool debug_break_parse(const String &p_file, int p_line, const String &p_error); @@ -341,9 +370,15 @@ public: void reload_assemblies(bool p_soft_reload); #endif - void project_assembly_loaded(); + _FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() { + return scripts_metadata_invalidated ? Dictionary() : scripts_metadata; + } - _FORCE_INLINE_ const Dictionary &get_scripts_metadata() { return scripts_metadata; } + _FORCE_INLINE_ const Dictionary &get_scripts_metadata() { + if (scripts_metadata_invalidated) + _load_scripts_metadata(); + return scripts_metadata; + } virtual String get_name() const; @@ -368,7 +403,6 @@ public: virtual bool supports_builtin_mode() const; /* TODO? */ virtual int find_function(const String &p_function, const String &p_code) const { return -1; } virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const; - /* TODO? */ Error complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, String &r_call_hint) { return ERR_UNAVAILABLE; } virtual String _get_indentation() const; /* TODO? */ virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {} /* TODO */ virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) {} @@ -429,7 +463,6 @@ public: }; class ResourceFormatLoaderCSharpScript : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderCSharpScript, ResourceFormatLoader) public: virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List<String> *p_extensions) const; @@ -438,7 +471,6 @@ public: }; class ResourceFormatSaverCSharpScript : public ResourceFormatSaver { - GDCLASS(ResourceFormatSaverCSharpScript, ResourceFormatSaver) public: virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const; diff --git a/modules/mono/doc_classes/@C#.xml b/modules/mono/doc_classes/@C#.xml index a821713d0d..826c106d7e 100644 --- a/modules/mono/doc_classes/@C#.xml +++ b/modules/mono/doc_classes/@C#.xml @@ -6,8 +6,6 @@ </description> <tutorials> </tutorials> - <demos> - </demos> <methods> </methods> <constants> diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml index 7f22388132..de2e246ea9 100644 --- a/modules/mono/doc_classes/CSharpScript.xml +++ b/modules/mono/doc_classes/CSharpScript.xml @@ -6,8 +6,6 @@ </description> <tutorials> </tutorials> - <demos> - </demos> <methods> <method name="new" qualifiers="vararg"> <return type="Object"> diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml index 21835e639c..18556a84ba 100644 --- a/modules/mono/doc_classes/GodotSharp.xml +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -6,8 +6,6 @@ </description> <tutorials> </tutorials> - <demos> - </demos> <methods> <method name="attach_thread"> <return type="void"> diff --git a/modules/mono/editor/GodotSharpTools/.gitignore b/modules/mono/editor/GodotSharpTools/.gitignore deleted file mode 100644 index 296ad48834..0000000000 --- a/modules/mono/editor/GodotSharpTools/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# nuget packages -packages
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs deleted file mode 100644 index 967e3bcc19..0000000000 --- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs +++ /dev/null @@ -1,425 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security; -using Microsoft.Build.Framework; - -namespace GodotSharpTools.Build -{ - public class BuildInstance : IDisposable - { - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode); - - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static string godot_icall_BuildInstance_get_MSBuildPath(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool godot_icall_BuildInstance_get_PrintBuildOutput(); - - private static string GetMSBuildPath() - { - string msbuildPath = godot_icall_BuildInstance_get_MSBuildPath(); - - if (msbuildPath == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); - - return msbuildPath; - } - - private static string MonoWindowsBinDir - { - get - { - string monoWinBinDir = godot_icall_BuildInstance_get_MonoWindowsBinDir(); - - if (monoWinBinDir == null) - throw new FileNotFoundException("Cannot find the Windows Mono binaries directory."); - - return monoWinBinDir; - } - } - - private static bool UsingMonoMSBuildOnWindows - { - get - { - return godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); - } - } - - private static bool PrintBuildOutput - { - get - { - return godot_icall_BuildInstance_get_PrintBuildOutput(); - } - } - - private string solution; - private string config; - - private Process process; - - private int exitCode; - public int ExitCode { get { return exitCode; } } - - public bool IsRunning { get { return process != null && !process.HasExited; } } - - public BuildInstance(string solution, string config) - { - this.solution = solution; - this.config = config; - } - - public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) - { - List<string> customPropertiesList = new List<string>(); - - if (customProperties != null) - customPropertiesList.AddRange(customProperties); - - string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - - ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - - bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput) // TODO: or if stdout verbose - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); - - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; - startInfo.UseShellExecute = false; - - if (UsingMonoMSBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } - - // Needed when running from Developer Command Prompt for VS - RemovePlatformVariable(startInfo.EnvironmentVariables); - - using (Process process = new Process()) - { - process.StartInfo = startInfo; - - process.Start(); - - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - - process.WaitForExit(); - - exitCode = process.ExitCode; - } - - return true; - } - - public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) - { - if (process != null) - throw new InvalidOperationException("Already in use"); - - List<string> customPropertiesList = new List<string>(); - - if (customProperties != null) - customPropertiesList.AddRange(customProperties); - - string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - - ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - - bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput) // TODO: or if stdout verbose - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); - - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; - startInfo.UseShellExecute = false; - - if (UsingMonoMSBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } - - // Needed when running from Developer Command Prompt for VS - RemovePlatformVariable(startInfo.EnvironmentVariables); - - process = new Process(); - process.StartInfo = startInfo; - process.EnableRaisingEvents = true; - process.Exited += new EventHandler(BuildProcess_Exited); - - process.Start(); - - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - - return true; - } - - private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties) - { - string arguments = string.Format(@"""{0}"" /v:normal /t:Build ""/p:{1}"" ""/l:{2},{3};{4}""", - solution, - "Configuration=" + config, - typeof(GodotBuildLogger).FullName, - loggerAssemblyPath, - loggerOutputDir - ); - - foreach (string customProperty in customProperties) - { - arguments += " \"/p:" + customProperty + "\""; - } - - return arguments; - } - - private void RemovePlatformVariable(StringDictionary environmentVariables) - { - // EnvironmentVariables is case sensitive? Seriously? - - List<string> platformEnvironmentVariables = new List<string>(); - - foreach (string env in environmentVariables.Keys) - { - if (env.ToUpper() == "PLATFORM") - platformEnvironmentVariables.Add(env); - } - - foreach (string env in platformEnvironmentVariables) - environmentVariables.Remove(env); - } - - private void BuildProcess_Exited(object sender, System.EventArgs e) - { - exitCode = process.ExitCode; - - godot_icall_BuildInstance_ExitCallback(solution, config, exitCode); - - Dispose(); - } - - private static bool IsDebugMSBuildRequested() - { - return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; - } - - public void Dispose() - { - if (process != null) - { - process.Dispose(); - process = null; - } - } - } - - public class GodotBuildLogger : ILogger - { - public string Parameters { get; set; } - public LoggerVerbosity Verbosity { get; set; } - - public void Initialize(IEventSource eventSource) - { - if (null == Parameters) - throw new LoggerException("Log directory was not set."); - - string[] parameters = Parameters.Split(new[] { ';' }); - - string logDir = parameters[0]; - - if (String.IsNullOrEmpty(logDir)) - throw new LoggerException("Log directory was not set."); - - if (parameters.Length > 1) - throw new LoggerException("Too many parameters passed."); - - string logFile = Path.Combine(logDir, "msbuild_log.txt"); - string issuesFile = Path.Combine(logDir, "msbuild_issues.csv"); - - try - { - if (!Directory.Exists(logDir)) - Directory.CreateDirectory(logDir); - - this.logStreamWriter = new StreamWriter(logFile); - this.issuesStreamWriter = new StreamWriter(issuesFile); - } - catch (Exception ex) - { - if - ( - ex is UnauthorizedAccessException - || ex is ArgumentNullException - || ex is PathTooLongException - || ex is DirectoryNotFoundException - || ex is NotSupportedException - || ex is ArgumentException - || ex is SecurityException - || ex is IOException - ) - { - throw new LoggerException("Failed to create log file: " + ex.Message); - } - else - { - // Unexpected failure - throw; - } - } - - eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted); - eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted); - eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised); - eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised); - eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised); - eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished); - } - - void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) - { - string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message); - - if (e.ProjectFile.Length > 0) - line += string.Format(" [{0}]", e.ProjectFile); - - WriteLine(line); - - string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}", - e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, - e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape()); - issuesStreamWriter.WriteLine(errorLine); - } - - void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) - { - string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile); - - if (e.ProjectFile != null && e.ProjectFile.Length > 0) - line += string.Format(" [{0}]", e.ProjectFile); - - WriteLine(line); - - string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}", - e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, - e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty); - issuesStreamWriter.WriteLine(warningLine); - } - - void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) - { - // BuildMessageEventArgs adds Importance to BuildEventArgs - // Let's take account of the verbosity setting we've been passed in deciding whether to log the message - if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)) - || (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)) - || (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) - ) - { - WriteLineWithSenderAndMessage(String.Empty, e); - } - } - - void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) - { - // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName - // To keep this log clean, this logger will ignore these events. - } - - void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) - { - WriteLine(e.Message); - indent++; - } - - void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) - { - indent--; - WriteLine(e.Message); - } - - /// <summary> - /// Write a line to the log, adding the SenderName - /// </summary> - private void WriteLineWithSender(string line, BuildEventArgs e) - { - if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) - { - // Well, if the sender name is MSBuild, let's leave it out for prettiness - WriteLine(line); - } - else - { - WriteLine(e.SenderName + ": " + line); - } - } - - /// <summary> - /// Write a line to the log, adding the SenderName and Message - /// (these parameters are on all MSBuild event argument objects) - /// </summary> - private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) - { - if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) - { - // Well, if the sender name is MSBuild, let's leave it out for prettiness - WriteLine(line + e.Message); - } - else - { - WriteLine(e.SenderName + ": " + line + e.Message); - } - } - - private void WriteLine(string line) - { - for (int i = indent; i > 0; i--) - { - logStreamWriter.Write("\t"); - } - logStreamWriter.WriteLine(line); - } - - public void Shutdown() - { - logStreamWriter.Close(); - issuesStreamWriter.Close(); - } - - public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) - { - return this.Verbosity >= checkVerbosity; - } - - private StreamWriter logStreamWriter; - private StreamWriter issuesStreamWriter; - private int indent; - } -} diff --git a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs deleted file mode 100644 index e45dd2025b..0000000000 --- a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; - -namespace GodotSharpTools.Editor -{ - public static class GodotSharpExport - { - public static void _ExportBegin(string[] features, bool debug, string path, int flags) - { - var featureSet = new HashSet<string>(features); - - if (PlatformHasTemplateDir(featureSet)) - { - string templateDirName = "data.mono"; - - if (featureSet.Contains("Windows")) - { - templateDirName += ".windows"; - templateDirName += featureSet.Contains("64") ? ".64" : ".32"; - } - else if (featureSet.Contains("X11")) - { - templateDirName += ".x11"; - templateDirName += featureSet.Contains("64") ? ".64" : ".32"; - } - else - { - throw new NotSupportedException("Target platform not supported"); - } - - templateDirName += debug ? ".release_debug" : ".release"; - - string templateDirPath = Path.Combine(GetTemplatesDir(), templateDirName); - - if (!Directory.Exists(templateDirPath)) - throw new FileNotFoundException("Data template directory not found"); - - string outputDir = new FileInfo(path).Directory.FullName; - - string outputDataDir = Path.Combine(outputDir, GetDataDirName()); - - if (Directory.Exists(outputDataDir)) - Directory.Delete(outputDataDir, recursive: true); // Clean first - - Directory.CreateDirectory(outputDataDir); - - foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); - } - - foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) - { - File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); - } - } - } - - public static bool PlatformHasTemplateDir(HashSet<string> featureSet) - { - // OSX export templates are contained in a zip, so we place - // our custom template inside it and let Godot do the rest. - return !featureSet.Contains("OSX"); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetTemplatesDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetDataDirName(); - } -} diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln deleted file mode 100644 index 5f7d0e8a39..0000000000 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln +++ /dev/null @@ -1,17 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpTools", "GodotSharpTools.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/modules/mono/editor/GodotSharpTools/Utils/OS.cs b/modules/mono/editor/GodotSharpTools/Utils/OS.cs deleted file mode 100644 index 148e954e77..0000000000 --- a/modules/mono/editor/GodotSharpTools/Utils/OS.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace GodotSharpTools.Utils -{ - public static class OS - { - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetPlatformName(); - - const string HaikuName = "Haiku"; - const string OSXName = "OSX"; - const string ServerName = "Server"; - const string UWPName = "UWP"; - const string WindowsName = "Windows"; - const string X11Name = "X11"; - - public static bool IsHaiku() - { - return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsOSX() - { - return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsServer() - { - return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsUWP() - { - return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsWindows() - { - return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsX11() - { - return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - static bool? IsUnixCache = null; - static readonly string[] UnixPlatforms = new string[] { HaikuName, OSXName, ServerName, X11Name }; - - public static bool IsUnix() - { - if (IsUnixCache.HasValue) - return IsUnixCache.Value; - - string osName = GetPlatformName(); - IsUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); - return IsUnixCache.Value; - } - } -} diff --git a/modules/mono/editor/GodotTools/.gitignore b/modules/mono/editor/GodotTools/.gitignore new file mode 100644 index 0000000000..48e2f914d8 --- /dev/null +++ b/modules/mono/editor/GodotTools/.gitignore @@ -0,0 +1,356 @@ +# Rider +.idea/ + +# Visual Studio Code +.vscode/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs new file mode 100644 index 0000000000..a0f6f1ff32 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -0,0 +1,186 @@ +using System; +using System.IO; +using System.Security; +using Microsoft.Build.Framework; +using GodotTools.Core; + +namespace GodotTools.BuildLogger +{ + public class GodotBuildLogger : ILogger + { + public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location); + + public string Parameters { get; set; } + public LoggerVerbosity Verbosity { get; set; } + + public void Initialize(IEventSource eventSource) + { + if (null == Parameters) + throw new LoggerException("Log directory was not set."); + + var parameters = Parameters.Split(new[] {';'}); + + string logDir = parameters[0]; + + if (string.IsNullOrEmpty(logDir)) + throw new LoggerException("Log directory was not set."); + + if (parameters.Length > 1) + throw new LoggerException("Too many parameters passed."); + + string logFile = Path.Combine(logDir, "msbuild_log.txt"); + string issuesFile = Path.Combine(logDir, "msbuild_issues.csv"); + + try + { + if (!Directory.Exists(logDir)) + Directory.CreateDirectory(logDir); + + logStreamWriter = new StreamWriter(logFile); + issuesStreamWriter = new StreamWriter(issuesFile); + } + catch (Exception ex) + { + if (ex is UnauthorizedAccessException + || ex is ArgumentNullException + || ex is PathTooLongException + || ex is DirectoryNotFoundException + || ex is NotSupportedException + || ex is ArgumentException + || ex is SecurityException + || ex is IOException) + { + throw new LoggerException("Failed to create log file: " + ex.Message); + } + else + { + // Unexpected failure + throw; + } + } + + eventSource.ProjectStarted += eventSource_ProjectStarted; + eventSource.TaskStarted += eventSource_TaskStarted; + eventSource.MessageRaised += eventSource_MessageRaised; + eventSource.WarningRaised += eventSource_WarningRaised; + eventSource.ErrorRaised += eventSource_ErrorRaised; + eventSource.ProjectFinished += eventSource_ProjectFinished; + } + + void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) + { + string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}"; + + if (e.ProjectFile.Length > 0) + line += $" [{e.ProjectFile}]"; + + WriteLine(line); + + string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}"; + issuesStreamWriter.WriteLine(errorLine); + } + + void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) + { + string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}"; + + if (!string.IsNullOrEmpty(e.ProjectFile)) + line += $" [{e.ProjectFile}]"; + + WriteLine(line); + + string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," + + $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}"; + issuesStreamWriter.WriteLine(warningLine); + } + + private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) + { + // BuildMessageEventArgs adds Importance to BuildEventArgs + // Let's take account of the verbosity setting we've been passed in deciding whether to log the message + if (e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal) + || e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal) + || e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) + { + WriteLineWithSenderAndMessage(string.Empty, e); + } + } + + private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) + { + // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName + // To keep this log clean, this logger will ignore these events. + } + + private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) + { + WriteLine(e.Message); + indent++; + } + + private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) + { + indent--; + WriteLine(e.Message); + } + + /// <summary> + /// Write a line to the log, adding the SenderName + /// </summary> + private void WriteLineWithSender(string line, BuildEventArgs e) + { + if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line); + } + else + { + WriteLine(e.SenderName + ": " + line); + } + } + + /// <summary> + /// Write a line to the log, adding the SenderName and Message + /// (these parameters are on all MSBuild event argument objects) + /// </summary> + private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) + { + if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line + e.Message); + } + else + { + WriteLine(e.SenderName + ": " + line + e.Message); + } + } + + private void WriteLine(string line) + { + for (int i = indent; i > 0; i--) + { + logStreamWriter.Write("\t"); + } + + logStreamWriter.WriteLine(line); + } + + public void Shutdown() + { + logStreamWriter.Close(); + issuesStreamWriter.Close(); + } + + private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) + { + return Verbosity >= checkVerbosity; + } + + private StreamWriter logStreamWriter; + private StreamWriter issuesStreamWriter; + private int indent; + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj new file mode 100644 index 0000000000..f3ac353c0f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GodotTools.BuildLogger</RootNamespace> + <AssemblyName>GodotTools.BuildLogger</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>portable</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="GodotBuildLogger.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8717c4901e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GodotTools.BuildLogger")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj new file mode 100644 index 0000000000..f36b40f87c --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>GodotTools.Core</RootNamespace> + <AssemblyName>GodotTools.Core</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ProcessExtensions.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="StringExtensions.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs new file mode 100644 index 0000000000..43d40f2ad9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace GodotTools.Core +{ + public static class ProcessExtensions + { + public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken)) + { + var tcs = new TaskCompletionSource<bool>(); + + void ProcessExited(object sender, EventArgs e) + { + tcs.TrySetResult(true); + } + + process.EnableRaisingEvents = true; + process.Exited += ProcessExited; + + try + { + if (process.HasExited) + return; + + using (cancellationToken.Register(() => tcs.TrySetCanceled())) + { + await tcs.Task; + } + } + finally + { + process.Exited -= ProcessExited; + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..699ae6e741 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("GodotTools.Core")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotSharpTools/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index b0436d2f18..8cd7e76303 100644 --- a/modules/mono/editor/GodotSharpTools/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; -namespace GodotSharpTools +namespace GodotTools.Core { public static class StringExtensions { @@ -25,7 +26,7 @@ namespace GodotSharpTools path = path.Replace('\\', '/'); - string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); @@ -37,18 +38,40 @@ namespace GodotSharpTools public static bool IsAbsolutePath(this string path) { return path.StartsWith("/", StringComparison.Ordinal) || - path.StartsWith("\\", StringComparison.Ordinal) || - path.StartsWith(driveRoot, StringComparison.Ordinal); + path.StartsWith("\\", StringComparison.Ordinal) || + path.StartsWith(driveRoot, StringComparison.Ordinal); } public static string CsvEscape(this string value, char delimiter = ',') { - bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1; + bool hasSpecialChar = value.IndexOfAny(new char[] {'\"', '\n', '\r', delimiter}) != -1; if (hasSpecialChar) return "\"" + value.Replace("\"", "\"\"") + "\""; return value; } + + public static string ToSafeDirName(this string dirName, bool allowDirSeparator) + { + var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"}; + + if (allowDirSeparator) + { + // Directory separators are allowed, but disallow ".." to avoid going up the filesystem + invalidChars.Add(".."); + } + else + { + invalidChars.Add("/"); + } + + string safeDirName = dirName.Replace("\\", "/").Trim(); + + foreach (string invalidChar in invalidChars) + safeDirName = safeDirName.Replace(invalidChar, "-"); + + return safeDirName; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs new file mode 100644 index 0000000000..345a472185 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs @@ -0,0 +1,15 @@ +namespace GodotTools +{ + public static class ApiAssemblyNames + { + public const string SolutionName = "GodotSharp"; + public const string Core = "GodotSharp"; + public const string Editor = "GodotSharpEditor"; + } + + public enum ApiAssemblyType + { + Core, + Editor + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs new file mode 100644 index 0000000000..bfae2afc13 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.IO; + +namespace GodotTools.ProjectEditor +{ + public static class ApiSolutionGenerator + { + public static void GenerateApiSolution(string solutionDir, + string coreProjDir, IEnumerable<string> coreCompileItems, + string editorProjDir, IEnumerable<string> editorCompileItems) + { + var solution = new DotNetSolution(ApiAssemblyNames.SolutionName); + + solution.DirectoryPath = solutionDir; + + // GodotSharp project + + const string coreApiAssemblyName = ApiAssemblyNames.Core; + + string coreGuid = ProjectGenerator.GenCoreApiProject(coreProjDir, coreCompileItems); + + var coreProjInfo = new DotNetSolution.ProjectInfo + { + Guid = coreGuid, + PathRelativeToSolution = Path.Combine(coreApiAssemblyName, $"{coreApiAssemblyName}.csproj") + }; + coreProjInfo.Configs.Add("Debug"); + coreProjInfo.Configs.Add("Release"); + + solution.AddNewProject(coreApiAssemblyName, coreProjInfo); + + // GodotSharpEditor project + + const string editorApiAssemblyName = ApiAssemblyNames.Editor; + + string editorGuid = ProjectGenerator.GenEditorApiProject(editorProjDir, + $"../{coreApiAssemblyName}/{coreApiAssemblyName}.csproj", editorCompileItems); + + var editorProjInfo = new DotNetSolution.ProjectInfo(); + editorProjInfo.Guid = editorGuid; + editorProjInfo.PathRelativeToSolution = Path.Combine(editorApiAssemblyName, $"{editorApiAssemblyName}.csproj"); + editorProjInfo.Configs.Add("Debug"); + editorProjInfo.Configs.Add("Release"); + + solution.AddNewProject(editorApiAssemblyName, editorProjInfo); + + // Save solution + + solution.Save(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs new file mode 100644 index 0000000000..76cb249acf --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -0,0 +1,122 @@ +using GodotTools.Core; +using System.Collections.Generic; +using System.IO; + +namespace GodotTools.ProjectEditor +{ + public class DotNetSolution + { + private string directoryPath; + private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>(); + + public string Name { get; } + + public string DirectoryPath + { + get => directoryPath; + set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); + } + + public class ProjectInfo + { + public string Guid; + public string PathRelativeToSolution; + public List<string> Configs = new List<string>(); + } + + public void AddNewProject(string name, ProjectInfo projectInfo) + { + projects[name] = projectInfo; + } + + public bool HasProject(string name) + { + return projects.ContainsKey(name); + } + + public ProjectInfo GetProjectInfo(string name) + { + return projects[name]; + } + + public bool RemoveProject(string name) + { + return projects.Remove(name); + } + + public void Save() + { + if (!Directory.Exists(DirectoryPath)) + throw new FileNotFoundException("The solution directory does not exist."); + + string projectsDecl = string.Empty; + string slnPlatformsCfg = string.Empty; + string projPlatformsCfg = string.Empty; + + bool isFirstProject = true; + + foreach (var pair in projects) + { + string name = pair.Key; + ProjectInfo projectInfo = pair.Value; + + if (!isFirstProject) + projectsDecl += "\n"; + + projectsDecl += string.Format(ProjectDeclaration, + name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid); + + for (int i = 0; i < projectInfo.Configs.Count; i++) + { + string config = projectInfo.Configs[i]; + + if (i != 0 || !isFirstProject) + { + slnPlatformsCfg += "\n"; + projPlatformsCfg += "\n"; + } + + slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config); + projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config); + } + + isFirstProject = false; + } + + string solutionPath = Path.Combine(DirectoryPath, Name + ".sln"); + string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); + + File.WriteAllText(solutionPath, content); + } + + public DotNetSolution(string name) + { + Name = name; + } + + const string SolutionTemplate = +@"Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +{0} +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution +{1} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution +{2} + EndGlobalSection +EndGlobal +"; + + const string ProjectDeclaration = +@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}"" +EndProject"; + + const string SolutionPlatformsConfig = +@" {0}|Any CPU = {0}|Any CPU"; + + const string ProjectPlatformsConfig = +@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU + {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; + } +} diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 2871c041f5..08b8ba3946 100644 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -1,12 +1,12 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> <OutputType>Library</OutputType> - <RootNamespace>GodotSharpTools</RootNamespace> - <AssemblyName>GodotSharpTools</AssemblyName> + <RootNamespace>GodotTools.ProjectEditor</RootNamespace> + <AssemblyName>GodotTools.ProjectEditor</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath> </PropertyGroup> @@ -21,7 +21,6 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>portable</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> @@ -31,25 +30,35 @@ <ItemGroup> <Reference Include="System" /> <Reference Include="Microsoft.Build" /> - <Reference Include="Microsoft.Build.Framework" /> <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL"> - <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <!-- + When building Godot with 'mono_glue=no' SCons will build this project alone instead of the + entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that. + We make SCons restore the NuGet packages in the project directory instead in this case. + --> + <HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? --> </Reference> </ItemGroup> <ItemGroup> - <Compile Include="StringExtensions.cs" /> - <Compile Include="Build\BuildSystem.cs" /> - <Compile Include="Editor\MonoDevelopInstance.cs" /> - <Compile Include="Project\ProjectExtensions.cs" /> - <Compile Include="Project\IdentifierUtils.cs" /> - <Compile Include="Project\ProjectGenerator.cs" /> - <Compile Include="Project\ProjectUtils.cs" /> + <Compile Include="ApiAssembliesInfo.cs" /> + <Compile Include="ApiSolutionGenerator.cs" /> + <Compile Include="DotNetSolution.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Utils\OS.cs" /> - <Compile Include="Editor\GodotSharpExport.cs" /> + <Compile Include="IdentifierUtils.cs" /> + <Compile Include="ProjectExtensions.cs" /> + <Compile Include="ProjectGenerator.cs" /> + <Compile Include="ProjectUtils.cs" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs index 83e2d2cf8d..f93eb9a1fa 100644 --- a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class IdentifierUtils { @@ -12,7 +12,7 @@ namespace GodotSharpTools.Project if (string.IsNullOrEmpty(qualifiedIdentifier)) throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier)); - string[] identifiers = qualifiedIdentifier.Split(new[] { '.' }); + string[] identifiers = qualifiedIdentifier.Split('.'); for (int i = 0; i < identifiers.Length; i++) { @@ -66,8 +66,6 @@ namespace GodotSharpTools.Project if (identifierBuilder.Length > startIndex || @char == '_') identifierBuilder.Append(@char); break; - default: - break; } } @@ -97,14 +95,14 @@ namespace GodotSharpTools.Project } else { - if (_doubleUnderscoreKeywords.Contains(value)) + if (DoubleUnderscoreKeywords.Contains(value)) return true; } - return _keywords.Contains(value); + return Keywords.Contains(value); } - static HashSet<string> _doubleUnderscoreKeywords = new HashSet<string> + private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string> { "__arglist", "__makeref", @@ -112,7 +110,7 @@ namespace GodotSharpTools.Project "__refvalue", }; - static HashSet<string> _keywords = new HashSet<string> + private static readonly HashSet<string> Keywords = new HashSet<string> { "as", "do", diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs index 647d9ac81d..36961eb45e 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs @@ -1,8 +1,9 @@ +using GodotTools.Core; using System; using DotNet.Globbing; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectExtensions { diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 89279c69a6..7cf58b6755 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,17 +1,19 @@ +using GodotTools.Core; using System; +using System.Collections.Generic; using System.IO; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectGenerator { - public const string CoreApiProjectName = "GodotSharp"; - public const string EditorApiProjectName = "GodotSharpEditor"; - const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"; - const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}"; + private const string CoreApiProjectName = "GodotSharp"; + private const string EditorApiProjectName = "GodotSharpEditor"; + private const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"; + private const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}"; - public static string GenCoreApiProject(string dir, string[] compileItems) + public static string GenCoreApiProject(string dir, IEnumerable<string> compileItems) { string path = Path.Combine(dir, CoreApiProjectName + ".csproj"); @@ -24,8 +26,8 @@ namespace GodotSharpTools.Project mainGroup.SetProperty("BaseIntermediateOutputPath", "obj"); GenAssemblyInfoFile(root, dir, CoreApiProjectName, - new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" }, - new string[] { "System.Runtime.CompilerServices" }); + new[] {"[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]"}, + new[] {"System.Runtime.CompilerServices"}); foreach (var item in compileItems) { @@ -37,7 +39,7 @@ namespace GodotSharpTools.Project return CoreApiProjectGuid; } - public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems) + public static string GenEditorApiProject(string dir, string coreApiProjPath, IEnumerable<string> compileItems) { string path = Path.Combine(dir, EditorApiProjectName + ".csproj"); @@ -64,7 +66,7 @@ namespace GodotSharpTools.Project return EditorApiProjectGuid; } - public static string GenGameProject(string dir, string name, string[] compileItems) + public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems) { string path = Path.Combine(dir, name + ".csproj"); @@ -74,23 +76,27 @@ namespace GodotSharpTools.Project mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); + mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' "; + mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' "; var toolsGroup = root.AddPropertyGroup(); toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' "; toolsGroup.AddProperty("DebugSymbols", "true"); toolsGroup.AddProperty("DebugType", "portable"); toolsGroup.AddProperty("Optimize", "false"); - toolsGroup.AddProperty("DefineConstants", "DEBUG;TOOLS;"); + toolsGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;"); toolsGroup.AddProperty("ErrorReport", "prompt"); toolsGroup.AddProperty("WarningLevel", "4"); toolsGroup.AddProperty("ConsolePause", "false"); var coreApiRef = root.AddItem("Reference", CoreApiProjectName); + coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("Private", "False"); var editorApiRef = root.AddItem("Reference", EditorApiProjectName); editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; + editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("Private", "False"); @@ -108,7 +114,6 @@ namespace GodotSharpTools.Project public static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null) { - string propertiesDir = Path.Combine(dir, "Properties"); if (!Directory.Exists(propertiesDir)) Directory.CreateDirectory(propertiesDir); @@ -124,12 +129,9 @@ namespace GodotSharpTools.Project string assemblyLinesText = string.Empty; if (assemblyLines != null) - { - foreach (var assemblyLine in assemblyLines) - assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; - } + assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; - string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); + string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs"); @@ -161,7 +163,7 @@ namespace GodotSharpTools.Project debugGroup.AddProperty("DebugSymbols", "true"); debugGroup.AddProperty("DebugType", "portable"); debugGroup.AddProperty("Optimize", "false"); - debugGroup.AddProperty("DefineConstants", "DEBUG;"); + debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;"); debugGroup.AddProperty("ErrorReport", "prompt"); debugGroup.AddProperty("WarningLevel", "4"); debugGroup.AddProperty("ConsolePause", "false"); @@ -170,6 +172,7 @@ namespace GodotSharpTools.Project releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "; releaseGroup.AddProperty("DebugType", "portable"); releaseGroup.AddProperty("Optimize", "true"); + releaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;"); releaseGroup.AddProperty("ErrorReport", "prompt"); releaseGroup.AddProperty("WarningLevel", "4"); releaseGroup.AddProperty("ConsolePause", "false"); @@ -193,8 +196,8 @@ namespace GodotSharpTools.Project } } - private const string assemblyInfoTemplate = -@"using System.Reflection;{0} + private const string AssemblyInfoTemplate = + @"using System.Reflection;{0} // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index a13f4fd6ef..22cf89695d 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -1,9 +1,10 @@ +using GodotTools.Core; using System.Collections.Generic; using System.IO; using DotNet.Globbing; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectUtils { @@ -53,7 +54,7 @@ namespace GodotSharpTools.Project var glob = Glob.Parse(normalizedInclude, globOptions); - // TODO Check somehow if path has no blog to avoid the following loop... + // TODO Check somehow if path has no blob to avoid the following loop... foreach (var existingFile in existingFiles) { diff --git a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs index 7115d8fc71..09333850fc 100644 --- a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs @@ -4,12 +4,12 @@ using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("GodotSharpTools")] +[assembly: AssemblyTitle("GodotTools.ProjectEditor")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("ignacio")] +[assembly: AssemblyCopyright("Godot Engine contributors")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/modules/mono/editor/GodotSharpTools/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config index 2c7cb0bd4b..13915000e4 100644 --- a/modules/mono/editor/GodotSharpTools/packages.config +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <packages> <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs new file mode 100644 index 0000000000..f849356919 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -0,0 +1,172 @@ +using GodotTools.Core; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using GodotTools.BuildLogger; +using GodotTools.Internals; +using GodotTools.Utils; +using Directory = System.IO.Directory; + +namespace GodotTools.Build +{ + public static class BuildSystem + { + private static string GetMsBuildPath() + { + string msbuildPath = MsBuildFinder.FindMsBuild(); + + if (msbuildPath == null) + throw new FileNotFoundException("Cannot find the MSBuild executable."); + + return msbuildPath; + } + + private static string MonoWindowsBinDir + { + get + { + string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin"); + + if (!Directory.Exists(monoWinBinDir)) + throw new FileNotFoundException("Cannot find the Windows Mono install bin directory."); + + return monoWinBinDir; + } + } + + private static Godot.EditorSettings EditorSettings => + GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + + private static bool UsingMonoMsBuildOnWindows + { + get + { + if (OS.IsWindows()) + { + return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") + == GodotSharpBuilds.BuildTool.MsBuildMono; + } + + return false; + } + } + + private static bool PrintBuildOutput => + (bool) EditorSettings.GetSetting("mono/builds/print_build_output"); + + private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + var customPropertiesList = new List<string>(); + + if (customProperties != null) + customPropertiesList.AddRange(customProperties); + + string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList); + + var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs); + + bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput; + + if (!redirectOutput || Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); + + startInfo.RedirectStandardOutput = redirectOutput; + startInfo.RedirectStandardError = redirectOutput; + startInfo.UseShellExecute = false; + + if (UsingMonoMsBuildOnWindows) + { + // These environment variables are required for Mono's MSBuild to find the compilers. + // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. + string monoWinBinDir = MonoWindowsBinDir; + startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); + startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); + startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); + } + + // Needed when running from Developer Command Prompt for VS + RemovePlatformVariable(startInfo.EnvironmentVariables); + + var process = new Process {StartInfo = startInfo}; + + process.Start(); + + if (redirectOutput) + { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + + return process; + } + + public static int Build(MonoBuildInfo monoBuildInfo) + { + return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration, + monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + } + + public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo) + { + return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration, + monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + } + + public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + { + process.WaitForExit(); + + return process.ExitCode; + } + } + + public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + { + await process.WaitForExitAsync(); + + return process.ExitCode; + } + } + + private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties) + { + string arguments = $@"""{solution}"" /v:normal /t:Rebuild ""/p:{"Configuration=" + config}"" " + + $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}"""; + + foreach (string customProperty in customProperties) + { + arguments += " /p:" + customProperty; + } + + return arguments; + } + + private static void RemovePlatformVariable(StringDictionary environmentVariables) + { + // EnvironmentVariables is case sensitive? Seriously? + + var platformEnvironmentVariables = new List<string>(); + + foreach (string env in environmentVariables.Keys) + { + if (env.ToUpper() == "PLATFORM") + platformEnvironmentVariables.Add(env); + } + + foreach (string env in platformEnvironmentVariables) + environmentVariables.Remove(env); + } + + private static bool IsDebugMsBuildRequested() + { + return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs new file mode 100644 index 0000000000..a0d14c43c9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Godot; +using GodotTools.Internals; +using Directory = System.IO.Directory; +using Environment = System.Environment; +using File = System.IO.File; +using Path = System.IO.Path; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Build +{ + public static class MsBuildFinder + { + private static string _msbuildToolsPath = string.Empty; + private static string _msbuildUnixPath = string.Empty; + private static string _xbuildUnixPath = string.Empty; + + public static string FindMsBuild() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); + + if (OS.IsWindows()) + { + switch (buildTool) + { + case GodotSharpBuilds.BuildTool.MsBuildVs: + { + if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _msbuildToolsPath = FindMsBuildToolsPathOnWindows(); + + if (_msbuildToolsPath.Empty()) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + } + } + + if (!_msbuildToolsPath.EndsWith("\\")) + _msbuildToolsPath += "\\"; + + return Path.Combine(_msbuildToolsPath, "MSBuild.exe"); + } + + case GodotSharpBuilds.BuildTool.MsBuildMono: + { + string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); + + if (!File.Exists(msbuildPath)) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); + } + + return msbuildPath; + } + + case GodotSharpBuilds.BuildTool.XBuild: + { + string xbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "xbuild.bat"); + + if (!File.Exists(xbuildPath)) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameXbuild}'. Tried with path: {xbuildPath}"); + } + + return xbuildPath; + } + + default: + throw new IndexOutOfRangeException("Invalid build tool in editor settings"); + } + } + + if (OS.IsUnix()) + { + if (buildTool == GodotSharpBuilds.BuildTool.XBuild) + { + if (_xbuildUnixPath.Empty() || !File.Exists(_xbuildUnixPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _xbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + } + + if (_xbuildUnixPath.Empty()) + { + throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameXbuild}'"); + } + } + else + { + if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _msbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + } + + if (_msbuildUnixPath.Empty()) + { + throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'"); + } + } + + return buildTool != GodotSharpBuilds.BuildTool.XBuild ? _msbuildUnixPath : _xbuildUnixPath; + } + + throw new PlatformNotSupportedException(); + } + + private static IEnumerable<string> MsBuildHintDirs + { + get + { + var result = new List<string>(); + + if (OS.IsOSX()) + { + result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); + result.Add("/usr/local/var/homebrew/linked/mono/bin/"); + } + + result.Add("/opt/novell/mono/bin/"); + + return result; + } + } + + private static string FindBuildEngineOnUnix(string name) + { + string ret = OS.PathWhich(name); + + if (!ret.Empty()) + return ret; + + string retFallback = OS.PathWhich($"{name}.exe"); + + if (!retFallback.Empty()) + return retFallback; + + foreach (string hintDir in MsBuildHintDirs) + { + string hintPath = Path.Combine(hintDir, name); + + if (File.Exists(hintPath)) + return hintPath; + } + + return string.Empty; + } + + private static string FindMsBuildToolsPathOnWindows() + { + if (!OS.IsWindows()) + throw new PlatformNotSupportedException(); + + // Try to find 15.0 with vswhere + + string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)"); + vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; + + var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; + + var outputArray = new Godot.Collections.Array<string>(); + int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, + blocking: true, output: (Godot.Collections.Array) outputArray); + + if (exitCode == 0) + return string.Empty; + + if (outputArray.Count == 0) + return string.Empty; + + var lines = outputArray[1].Split('\n'); + + foreach (string line in lines) + { + int sepIdx = line.IndexOf(':'); + + if (sepIdx <= 0) + continue; + + string key = line.Substring(0, sepIdx); // No need to trim + + if (key != "installationPath") + continue; + + string value = line.Substring(sepIdx + 1).StripEdges(); + + if (value.Empty()) + throw new FormatException("installationPath value is empty"); + + if (!value.EndsWith("\\")) + value += "\\"; + + // Since VS2019, the directory is simply named "Current" + string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin"); + + if (Directory.Exists(msbuildDir)) + return msbuildDir; + + // Directory name "15.0" is used in VS 2017 + return Path.Combine(value, "MSBuild\\15.0\\Bin"); + } + + return string.Empty; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs new file mode 100644 index 0000000000..3ba311c283 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs @@ -0,0 +1,115 @@ +using Godot; +using System; +using System.Collections.Generic; +using Godot.Collections; +using GodotTools.Internals; +using GodotTools.ProjectEditor; +using File = GodotTools.Utils.File; +using Directory = GodotTools.Utils.Directory; + +namespace GodotTools +{ + public static class CSharpProject + { + public static string GenerateGameProject(string dir, string name, IEnumerable<string> files = null) + { + try + { + return ProjectGenerator.GenGameProject(dir, name, files); + } + catch (Exception e) + { + GD.PushError(e.ToString()); + return string.Empty; + } + } + + public static void AddItem(string projectPath, string itemType, string include) + { + if (!(bool) Internal.GlobalDef("mono/project/auto_update_project", true)) + return; + + ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include); + } + + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private static ulong ConvertToTimestamp(this DateTime value) + { + TimeSpan elapsedTime = value - Epoch; + return (ulong) elapsedTime.TotalSeconds; + } + + public static void GenerateScriptsMetadata(string projectPath, string outputPath) + { + if (File.Exists(outputPath)) + File.Delete(outputPath); + + var oldDict = Internal.GetScriptsMetadataOrNothing(); + var newDict = new Godot.Collections.Dictionary<string, object>(); + + foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile")) + { + string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath(); + + ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp(); + + if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar)) + { + var oldFileDict = (Dictionary) oldFileVar; + + if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime)) + { + if (storedModifiedTime == modifiedTime) + { + // No changes so no need to parse again + newDict[projectIncludeFile] = oldFileDict; + continue; + } + } + } + + ScriptClassParser.ParseFileOrThrow(projectIncludeFile, out var classes); + + string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile); + + var classDict = new Dictionary(); + + foreach (var classDecl in classes) + { + if (classDecl.BaseCount == 0) + continue; // Does not inherit nor implement anything, so it can't be a script class + + string classCmp = classDecl.Nested ? + classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) : + classDecl.Name; + + if (classCmp != searchName) + continue; + + classDict["namespace"] = classDecl.Namespace; + classDict["class_name"] = classDecl.Name; + classDict["nested"] = classDecl.Nested; + break; + } + + if (classDict.Count == 0) + continue; // Not found + + newDict[projectIncludeFile] = new Dictionary {["modified_time"] = $"{modifiedTime}", ["class"] = classDict}; + } + + if (newDict.Count > 0) + { + string json = JSON.Print(newDict); + + string baseDir = outputPath.GetBaseDir(); + + if (!Directory.Exists(baseDir)) + Directory.CreateDirectory(baseDir); + + File.WriteAllText(outputPath, json); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs new file mode 100644 index 0000000000..433a931941 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using GodotTools.Build; +using GodotTools.Internals; +using GodotTools.Utils; +using Error = Godot.Error; +using File = GodotTools.Utils.File; +using Directory = GodotTools.Utils.Directory; + +namespace GodotTools +{ + public static class GodotSharpBuilds + { + private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>(); + + public const string PropNameMsbuildMono = "MSBuild (Mono)"; + public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)"; + public const string PropNameXbuild = "xbuild (Deprecated)"; + + public const string MsBuildIssuesFileName = "msbuild_issues.csv"; + public const string MsBuildLogFileName = "msbuild_log.txt"; + + public enum BuildTool + { + MsBuildMono, + MsBuildVs, + XBuild // Deprecated + } + + private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo) + { + var issuesFile = GetIssuesFilePath(buildInfo); + + if (!File.Exists(issuesFile)) + return; + + File.Delete(issuesFile); + } + + private static string _ApiFolderName(ApiAssemblyType apiType) + { + ulong apiHash = apiType == ApiAssemblyType.Core ? + Internal.GetCoreApiHash() : + Internal.GetEditorApiHash(); + return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}"; + } + + private static void ShowBuildErrorDialog(string message) + { + GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); + GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab(); + } + + public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + + private static string GetLogFilePath(MonoBuildInfo buildInfo) + { + return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); + } + + private static string GetIssuesFilePath(MonoBuildInfo buildInfo) + { + return Path.Combine(Godot.ProjectSettings.LocalizePath(buildInfo.LogsDirPath), MsBuildIssuesFileName); + } + + private static void PrintVerbose(string text) + { + if (Godot.OS.IsStdoutVerbose()) + Godot.GD.Print(text); + } + + public static bool Build(MonoBuildInfo buildInfo) + { + if (BuildsInProgress.Contains(buildInfo)) + throw new InvalidOperationException("A build is already in progress"); + + BuildsInProgress.Add(buildInfo); + + try + { + MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + buildTab.OnBuildStart(); + + // Required in order to update the build tasks list + Internal.GodotMainIteration(); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = BuildSystem.Build(buildInfo); + + if (exitCode != 0) + PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); + + buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + + return exitCode == 0; + } + catch (Exception e) + { + buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + BuildsInProgress.Remove(buildInfo); + } + } + + public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo) + { + if (BuildsInProgress.Contains(buildInfo)) + throw new InvalidOperationException("A build is already in progress"); + + BuildsInProgress.Add(buildInfo); + + try + { + MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = await BuildSystem.BuildAsync(buildInfo); + + if (exitCode != 0) + PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); + + buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + + return exitCode == 0; + } + catch (Exception e) + { + buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + BuildsInProgress.Remove(buildInfo); + } + } + + public static bool BuildApiSolution(string apiSlnDir, string config) + { + string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln"); + + string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config); + string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll"); + + string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config); + string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll"); + + if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile)) + return true; // The assemblies are in the output folder; assume the solution is already built + + var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config); + + // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, + // once we start to actively document manually maintained C# classes + apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings + + if (Build(apiBuildInfo)) + return true; + + ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution."); + return false; + } + + private static bool CopyApiAssembly(string srcDir, string dstDir, string assemblyName, ApiAssemblyType apiType) + { + // Create destination directory if needed + if (!Directory.Exists(dstDir)) + { + try + { + Directory.CreateDirectory(dstDir); + } + catch (IOException e) + { + ShowBuildErrorDialog($"Failed to create destination directory for the API assemblies. Exception message: {e.Message}"); + return false; + } + } + + string assemblyFile = assemblyName + ".dll"; + string assemblySrc = Path.Combine(srcDir, assemblyFile); + string assemblyDst = Path.Combine(dstDir, assemblyFile); + + if (!File.Exists(assemblyDst) || File.GetLastWriteTime(assemblySrc) > File.GetLastWriteTime(assemblyDst) || + Internal.MetadataIsApiAssemblyInvalidated(apiType)) + { + string xmlFile = $"{assemblyName}.xml"; + string pdbFile = $"{assemblyName}.pdb"; + + try + { + File.Copy(Path.Combine(srcDir, xmlFile), Path.Combine(dstDir, xmlFile)); + } + catch (IOException e) + { + Godot.GD.PushWarning(e.ToString()); + } + + try + { + File.Copy(Path.Combine(srcDir, pdbFile), Path.Combine(dstDir, pdbFile)); + } + catch (IOException e) + { + Godot.GD.PushWarning(e.ToString()); + } + + try + { + File.Copy(assemblySrc, assemblyDst); + } + catch (IOException e) + { + ShowBuildErrorDialog($"Failed to copy {assemblyFile}. Exception message: {e.Message}"); + return false; + } + + Internal.MetadataSetApiAssemblyInvalidated(apiType, false); + } + + return true; + } + + public static bool MakeApiAssembly(ApiAssemblyType apiType, string config) + { + string apiName = apiType == ApiAssemblyType.Core ? ApiAssemblyNames.Core : ApiAssemblyNames.Editor; + + string editorPrebuiltApiDir = Path.Combine(GodotSharpDirs.DataEditorPrebuiltApiDir, config); + string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, config); + + if (File.Exists(Path.Combine(editorPrebuiltApiDir, $"{apiName}.dll"))) + { + using (var copyProgress = new EditorProgress("mono_copy_prebuilt_api_assembly", $"Copying prebuilt {apiName} assembly...", 1)) + { + copyProgress.Step($"Copying {apiName} assembly", 0); + return CopyApiAssembly(editorPrebuiltApiDir, resAssembliesDir, apiName, apiType); + } + } + + const string apiSolutionName = ApiAssemblyNames.SolutionName; + + using (var pr = new EditorProgress($"mono_build_release_{apiSolutionName}", $"Building {apiSolutionName} solution...", 3)) + { + pr.Step($"Generating {apiSolutionName} solution", 0); + + string apiSlnDir = Path.Combine(GodotSharpDirs.MonoSolutionsDir, _ApiFolderName(ApiAssemblyType.Core)); + string apiSlnFile = Path.Combine(apiSlnDir, $"{apiSolutionName}.sln"); + + if (!Directory.Exists(apiSlnDir) || !File.Exists(apiSlnFile)) + { + var bindingsGenerator = new BindingsGenerator(); + + if (!Godot.OS.IsStdoutVerbose()) + bindingsGenerator.LogPrintEnabled = false; + + Error err = bindingsGenerator.GenerateCsApi(apiSlnDir); + if (err != Error.Ok) + { + ShowBuildErrorDialog($"Failed to generate {apiSolutionName} solution. Error: {err}"); + return false; + } + } + + pr.Step($"Building {apiSolutionName} solution", 1); + + if (!BuildApiSolution(apiSlnDir, config)) + return false; + + pr.Step($"Copying {apiName} assembly", 2); + + // Copy the built assembly to the assemblies directory + string apiAssemblyDir = Path.Combine(apiSlnDir, apiName, "bin", config); + if (!CopyApiAssembly(apiAssemblyDir, resAssembliesDir, apiName, apiType)) + return false; + } + + return true; + } + + public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines) + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return true; // No solution to build + + string apiConfig = config == "Release" ? "Release" : "Debug"; + + if (!MakeApiAssembly(ApiAssemblyType.Core, apiConfig)) + return false; + + if (!MakeApiAssembly(ApiAssemblyType.Editor, apiConfig)) + return false; + + using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) + { + pr.Step("Building project solution", 0); + + var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config); + + // Add Godot defines + string constants = OS.IsWindows() ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; + + foreach (var godotDefine in godotDefines) + constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};"; + + if (Internal.GodotIsRealTDouble()) + constants += "GODOT_REAL_T_IS_DOUBLE;"; + + constants += OS.IsWindows() ? "\"" : "\\\""; + + buildInfo.CustomProperties.Add(constants); + + if (!Build(buildInfo)) + { + ShowBuildErrorDialog("Failed to build project solution"); + return false; + } + } + + return true; + } + + public static bool EditorBuildCallback() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return true; // No solution to build + + string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); + string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); + + CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + + if (File.Exists(editorScriptsMetadataPath)) + File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + + var godotDefines = new[] + { + Godot.OS.GetName(), + Internal.GodotIs32Bits() ? "32" : "64" + }; + + return BuildProjectBlocking("Tools", godotDefines); + } + + public static void Initialize() + { + // Build tool settings + + Internal.EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono); + + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Godot.Variant.Type.Int, + ["name"] = "mono/builds/build_tool", + ["hint"] = Godot.PropertyHint.Enum, + ["hint_string"] = OS.IsWindows() ? + $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameXbuild}" : + $"{PropNameMsbuildMono},{PropNameXbuild}" + }); + + Internal.EditorDef("mono/builds/print_build_output", false); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs new file mode 100644 index 0000000000..955574d5fe --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -0,0 +1,538 @@ +using Godot; +using GodotTools.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using GodotTools.Internals; +using GodotTools.ProjectEditor; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; +using OS = GodotTools.Utils.OS; + +namespace GodotTools +{ + public class GodotSharpEditor : EditorPlugin, ISerializationListener + { + private EditorSettings editorSettings; + + private PopupMenu menuPopup; + + private AcceptDialog errorDialog; + private AcceptDialog aboutDialog; + private CheckBox aboutDialogCheckBox; + + private ToolButton bottomPanelBtn; + + private MonoDevelopInstance monoDevelopInstance; + private MonoDevelopInstance visualStudioForMacInstance; + + public MonoBottomPanel MonoBottomPanel { get; private set; } + + private bool CreateProjectSolution() + { + using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...", 2)) // TTR("Generating solution...") + { + pr.Step("Generating C# project..."); // TTR("Generating C# project...") + + string resourceDir = ProjectSettings.GlobalizePath("res://"); + + string path = resourceDir; + string name = (string) ProjectSettings.GetSetting("application/config/name"); + if (name.Empty()) + name = "UnnamedProject"; + + string guid = CSharpProject.GenerateGameProject(path, name); + + if (guid.Length > 0) + { + var solution = new DotNetSolution(name) + { + DirectoryPath = path + }; + + var projectInfo = new DotNetSolution.ProjectInfo + { + Guid = guid, + PathRelativeToSolution = name + ".csproj", + Configs = new List<string> {"Debug", "Release", "Tools"} + }; + + solution.AddNewProject(name, projectInfo); + + try + { + solution.Save(); + } + catch (IOException e) + { + ShowErrorDialog($"Failed to save solution. Exception message: {e.Message}"); // TTR + return false; + } + + string apiConfig = "Debug"; + + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, apiConfig)) + return false; + + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, apiConfig)) + return false; + + pr.Step("Done"); // TTR("Done") + + // Here, after all calls to progress_task_step + CallDeferred(nameof(_RemoveCreateSlnMenuOption)); + } + else + { + ShowErrorDialog("Failed to create C# project."); // TTR + } + + return true; + } + } + + private static int _makeApiSolutionsAttempts = 100; + private static bool _makeApiSolutionsRecursionGuard = false; + + private void _MakeApiSolutionsIfNeeded() + { + // I'm sick entirely of ProgressDialog + + if (Internal.IsMessageQueueFlushing() || Engine.GetMainLoop() == null) + { + if (_makeApiSolutionsAttempts == 0) // This better never happen or I swear... + throw new TimeoutException(); + + if (Engine.GetMainLoop() != null) + { + if (!Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded))) + Engine.GetMainLoop().Connect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)); + } + else + { + CallDeferred(nameof(_MakeApiSolutionsIfNeededImpl)); + } + + _makeApiSolutionsAttempts--; + return; + } + + // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike + // the message queue, with signals the collateral damage should be minimal in the worst case. + if (!_makeApiSolutionsRecursionGuard) + { + _makeApiSolutionsRecursionGuard = true; + + // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead + if (Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded))) + Engine.GetMainLoop().Disconnect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)); + + _MakeApiSolutionsIfNeededImpl(); + + _makeApiSolutionsRecursionGuard = false; + } + } + + private void _MakeApiSolutionsIfNeededImpl() + { + // If the project has a solution and C# project make sure the API assemblies are present and up to date + + string api_config = "Debug"; + string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, api_config); + + if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Core}.dll")) || + Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Core)) + { + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, api_config)) + return; + } + + if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Editor}.dll")) || + Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Editor)) + { + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, api_config)) + return; // Redundant? I don't think so! + } + } + + private void _RemoveCreateSlnMenuOption() + { + menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln)); + bottomPanelBtn.Show(); + } + + private void _ShowAboutDialog() + { + bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + aboutDialogCheckBox.Pressed = showOnStart; + aboutDialog.PopupCenteredMinsize(); + } + + private void _ToggleAboutDialogOnStart(bool enabled) + { + bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showOnStart != enabled) + editorSettings.SetSetting("mono/editor/show_info_on_start", enabled); + } + + private void _MenuOptionPressed(MenuOptions id) + { + switch (id) + { + case MenuOptions.CreateSln: + CreateProjectSolution(); + break; + case MenuOptions.AboutCSharp: + _ShowAboutDialog(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option"); + } + } + + private void _BuildSolutionPressed() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + { + if (!CreateProjectSolution()) + return; // Failed to create solution + } + + Instance.MonoBottomPanel.BuildProjectPressed(); + } + + public override void _Notification(int what) + { + base._Notification(what); + + if (what == NotificationReady) + { + bool showInfoDialog = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showInfoDialog) + { + aboutDialog.PopupExclusive = true; + _ShowAboutDialog(); + // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on. + aboutDialog.PopupExclusive = false; + } + } + } + + public enum MenuOptions + { + CreateSln, + AboutCSharp, + } + + public enum ExternalEditor + { + None, + VisualStudio, // TODO (Windows-only) + VisualStudioForMac, // Mac-only + MonoDevelop, + VsCode + } + + public void ShowErrorDialog(string message, string title = "Error") + { + errorDialog.WindowTitle = title; + errorDialog.DialogText = message; + errorDialog.PopupCenteredMinsize(); + } + + private static string _vsCodePath = string.Empty; + + private static readonly string[] VsCodeNames = + { + "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss" + }; + + public Error OpenInExternalEditor(Script script, int line, int col) + { + var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor"); + + switch (editor) + { + case ExternalEditor.VsCode: + { + if (_vsCodePath.Empty() || !File.Exists(_vsCodePath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty); + } + + var args = new List<string>(); + + bool osxAppBundleInstalled = false; + + if (OS.IsOSX()) + { + // The package path is '/Applications/Visual Studio Code.app' + const string vscodeBundleId = "com.microsoft.VSCode"; + + osxAppBundleInstalled = Internal.IsOsxAppBundleInstalled(vscodeBundleId); + + if (osxAppBundleInstalled) + { + args.Add("-b"); + args.Add(vscodeBundleId); + + // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is + // editing our folder. It's better to ask for a new window and let VSCode do the window management. + args.Add("-n"); + + // The open process must wait until the application finishes (which is instant in VSCode's case) + args.Add("--wait-apps"); + + args.Add("--args"); + } + } + + var resourcePath = ProjectSettings.GlobalizePath("res://"); + args.Add(resourcePath); + + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + { + args.Add("-g"); + args.Add($"{scriptPath}:{line + 1}:{col}"); + } + else + { + args.Add(scriptPath); + } + + string command; + + if (OS.IsOSX()) + { + if (!osxAppBundleInstalled && _vsCodePath.Empty()) + { + GD.PushError("Cannot find code editor: VSCode"); + return Error.FileNotFound; + } + + command = osxAppBundleInstalled ? "/usr/bin/open" : _vsCodePath; + } + else + { + if (_vsCodePath.Empty()) + { + GD.PushError("Cannot find code editor: VSCode"); + return Error.FileNotFound; + } + + command = _vsCodePath; + } + + try + { + OS.RunProcess(command, args); + } + catch (Exception e) + { + GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'"); + } + + break; + } + + case ExternalEditor.VisualStudioForMac: + goto case ExternalEditor.MonoDevelop; + case ExternalEditor.MonoDevelop: + { + MonoDevelopInstance GetMonoDevelopInstance(string solutionPath) + { + if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac) + { + if (visualStudioForMacInstance == null) + visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac); + + return visualStudioForMacInstance; + } + + if (monoDevelopInstance == null) + monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop); + + return monoDevelopInstance; + } + + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + scriptPath += $";{line + 1};{col}"; + + GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); + + break; + } + + case ExternalEditor.None: + return Error.Unavailable; + default: + throw new ArgumentOutOfRangeException(); + } + + return Error.Ok; + } + + public bool OverridesExternalEditor() + { + return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None; + } + + public override bool Build() + { + return GodotSharpBuilds.EditorBuildCallback(); + } + + public override void EnablePlugin() + { + base.EnablePlugin(); + + if (Instance != null) + throw new InvalidOperationException(); + Instance = this; + + var editorInterface = GetEditorInterface(); + var editorBaseControl = editorInterface.GetBaseControl(); + + editorSettings = editorInterface.GetEditorSettings(); + + errorDialog = new AcceptDialog(); + editorBaseControl.AddChild(errorDialog); + + MonoBottomPanel = new MonoBottomPanel(); + + bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono"); // TTR("Mono") + + AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); + + menuPopup = new PopupMenu(); + menuPopup.Hide(); + menuPopup.SetAsToplevel(true); + + AddToolSubmenuItem("Mono", menuPopup); + + // TODO: Remove or edit this info dialog once Mono support is no longer in alpha + { + menuPopup.AddItem("About C# support", (int) MenuOptions.AboutCSharp); // TTR("About C# support") + aboutDialog = new AcceptDialog(); + editorBaseControl.AddChild(aboutDialog); + aboutDialog.WindowTitle = "Important: C# support is not feature-complete"; + + // We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox + // we'll add. Instead we add containers and a new autowrapped Label inside. + + // Main VBoxContainer (icon + label on top, checkbox at bottom) + var aboutVBox = new VBoxContainer(); + aboutDialog.AddChild(aboutVBox); + + // HBoxContainer for icon + label + var aboutHBox = new HBoxContainer(); + aboutVBox.AddChild(aboutHBox); + + var aboutIcon = new TextureRect(); + aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons"); + aboutHBox.AddChild(aboutIcon); + + var aboutLabel = new Label(); + aboutHBox.AddChild(aboutLabel); + aboutLabel.RectMinSize = new Vector2(600, 150) * Internal.EditorScale; + aboutLabel.SizeFlagsVertical = (int) Control.SizeFlags.ExpandFill; + aboutLabel.Autowrap = true; + aboutLabel.Text = + "C# support in Godot Engine is in late alpha stage and, while already usable, " + + "it is not meant for use in production.\n\n" + + "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + + "Bugs and usability issues will be addressed gradually over future releases, " + + "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + + " https://github.com/godotengine/godot/issues\n\n" + + "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; + + Internal.EditorDef("mono/editor/show_info_on_start", true); + + // CheckBox in main container + aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"}; + aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart)); + aboutVBox.AddChild(aboutDialogCheckBox); + } + + if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) + { + // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized. + CallDeferred(nameof(_MakeApiSolutionsIfNeeded)); + } + else + { + bottomPanelBtn.Hide(); + menuPopup.AddItem("Create C# solution", (int) MenuOptions.CreateSln); // TTR("Create C# solution") + } + + menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed)); + + var buildButton = new ToolButton + { + Text = "Build", + HintTooltip = "Build solution", + FocusMode = Control.FocusModeEnum.None + }; + buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed)); + AddControlToContainer(CustomControlContainer.Toolbar, buildButton); + + // External editor settings + Internal.EditorDef("mono/editor/external_editor", ExternalEditor.None); + + string settingsHintStr = "Disabled"; + + if (OS.IsWindows()) + { + settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + else if (OS.IsOSX()) + { + settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" + + $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + else if (OS.IsUnix()) + { + settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Variant.Type.Int, + ["name"] = "mono/editor/external_editor", + ["hint"] = PropertyHint.Enum, + ["hint_string"] = settingsHintStr + }); + + // Export plugin + AddExportPlugin(new GodotSharpExport()); + + GodotSharpBuilds.Initialize(); + } + + public void OnBeforeSerialize() + { + } + + public void OnAfterDeserialize() + { + Instance = this; + } + + // Singleton + + public static GodotSharpEditor Instance { get; private set; } + + private GodotSharpEditor() + { + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs new file mode 100644 index 0000000000..b80fe1fab7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs @@ -0,0 +1,197 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using GodotTools.Core; +using GodotTools.Internals; +using Directory = GodotTools.Utils.Directory; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class GodotSharpExport : EditorExportPlugin + { + private void AddFile(string srcPath, string dstPath, bool remap = false) + { + AddFile(dstPath, File.ReadAllBytes(srcPath), remap); + } + + public override void _ExportFile(string path, string type, string[] features) + { + base._ExportFile(path, type, features); + + if (type != Internal.CSharpLanguageType) + return; + + if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}") + throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); + + // TODO What if the source file is not part of the game's C# project + + bool includeScriptsContent = (bool) ProjectSettings.GetSetting("mono/export/include_scripts_content"); + + if (!includeScriptsContent) + { + // We don't want to include the source code on exported games + AddFile(path, new byte[] { }, remap: false); + Skip(); + } + } + + public override void _ExportBegin(string[] features, bool isDebug, string path, int flags) + { + base._ExportBegin(features, isDebug, path, flags); + + try + { + _ExportBeginImpl(features, isDebug, path, flags); + } + catch (Exception e) + { + GD.PushError($"Failed to export project. Exception message: {e.Message}"); + Console.Error.WriteLine(e); + } + } + + public void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) + { + // TODO Right now there is no way to stop the export process with an error + + if (File.Exists(GodotSharpDirs.ProjectSlnPath)) + { + string buildConfig = isDebug ? "Debug" : "Release"; + + string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); + CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + + AddFile(scriptsMetadataPath, scriptsMetadataPath); + + // Turn export features into defines + var godotDefines = features; + + if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines)) + { + GD.PushError("Failed to build project"); + return; + } + + // Add dependency assemblies + + var dependencies = new Godot.Collections.Dictionary<string, string>(); + + var projectDllName = (string) ProjectSettings.GetSetting("application/config/name"); + if (projectDllName.Empty()) + { + projectDllName = "UnnamedProject"; + } + + string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); + string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); + + dependencies[projectDllName] = projectDllSrcPath; + + { + string templatesDir = Internal.FullTemplatesDir; + string androidBclDir = Path.Combine(templatesDir, "android-bcl"); + + string customLibDir = features.Contains("Android") && Directory.Exists(androidBclDir) ? androidBclDir : string.Empty; + + GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies); + } + + string apiConfig = isDebug ? "Debug" : "Release"; + string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); + + foreach (var dependency in dependencies) + { + string dependSrcPath = dependency.Value; + string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile()); + AddFile(dependSrcPath, dependDstPath); + } + } + + // Mono specific export template extras (data dir) + ExportDataDirectory(features, isDebug, path); + } + + private static void ExportDataDirectory(IEnumerable<string> features, bool debug, string path) + { + var featureSet = new HashSet<string>(features); + + if (!PlatformHasTemplateDir(featureSet)) + return; + + string templateDirName = "data.mono"; + + if (featureSet.Contains("Windows")) + { + templateDirName += ".windows"; + templateDirName += featureSet.Contains("64") ? ".64" : ".32"; + } + else if (featureSet.Contains("X11")) + { + templateDirName += ".x11"; + templateDirName += featureSet.Contains("64") ? ".64" : ".32"; + } + else + { + throw new NotSupportedException("Target platform not supported"); + } + + templateDirName += debug ? ".release_debug" : ".release"; + + string templateDirPath = Path.Combine(Internal.FullTemplatesDir, templateDirName); + + if (!Directory.Exists(templateDirPath)) + throw new FileNotFoundException("Data template directory not found"); + + string outputDir = new FileInfo(path).Directory?.FullName ?? + throw new FileNotFoundException("Base directory not found"); + + string outputDataDir = Path.Combine(outputDir, DataDirName); + + if (Directory.Exists(outputDataDir)) + Directory.Delete(outputDataDir, recursive: true); // Clean first + + Directory.CreateDirectory(outputDataDir); + + foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) + { + Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); + } + + foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) + { + File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); + } + } + + private static bool PlatformHasTemplateDir(IEnumerable<string> featureSet) + { + // OSX export templates are contained in a zip, so we place + // our custom template inside it and let Godot do the rest. + return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f)); + } + + private static string DataDirName + { + get + { + var appName = (string) ProjectSettings.GetSetting("application/config/name"); + string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); + return $"data_{appNameSafe}"; + } + } + + private static void GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath, + string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies) => + internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath, + string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj new file mode 100644 index 0000000000..a0ff8a0df1 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>GodotTools</RootNamespace> + <AssemblyName>GodotTools</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> + <GodotApiConfiguration>Debug</GodotApiConfiguration> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="GodotSharp"> + <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath> + </Reference> + <Reference Include="GodotSharpEditor"> + <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="Internals\BindingsGenerator.cs" /> + <Compile Include="Internals\EditorProgress.cs" /> + <Compile Include="Internals\GodotSharpDirs.cs" /> + <Compile Include="Internals\Internal.cs" /> + <Compile Include="Internals\ScriptClassParser.cs" /> + <Compile Include="MonoDevelopInstance.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Build\BuildSystem.cs" /> + <Compile Include="Utils\Directory.cs" /> + <Compile Include="Utils\File.cs" /> + <Compile Include="Utils\OS.cs" /> + <Compile Include="GodotSharpEditor.cs" /> + <Compile Include="GodotSharpBuilds.cs" /> + <Compile Include="HotReloadAssemblyWatcher.cs" /> + <Compile Include="MonoBuildInfo.cs" /> + <Compile Include="MonoBuildTab.cs" /> + <Compile Include="MonoBottomPanel.cs" /> + <Compile Include="GodotSharpExport.cs" /> + <Compile Include="CSharpProject.cs" /> + <Compile Include="Utils\CollectionExtensions.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj"> + <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project> + <Name>GodotTools.BuildLogger</Name> + </ProjectReference> + <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj"> + <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project> + <Name>GodotTools.ProjectEditor</Name> + </ProjectReference> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Folder Include="Editor" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs new file mode 100644 index 0000000000..aa52079cf4 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -0,0 +1,47 @@ +using Godot; +using GodotTools.Internals; + +namespace GodotTools +{ + public class HotReloadAssemblyWatcher : Node + { + private Timer watchTimer; + + public override void _Notification(int what) + { + if (what == MainLoop.NotificationWmFocusIn) + { + RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + } + + private void TimerTimeout() + { + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + public void RestartTimer() + { + watchTimer.Stop(); + watchTimer.Start(); + } + + public override void _Ready() + { + base._Ready(); + + watchTimer = new Timer + { + OneShot = false, + WaitTime = (float) Internal.EditorDef("mono/assembly_watch_interval_sec", 0.5) + }; + watchTimer.Connect("timeout", this, nameof(TimerTimeout)); + AddChild(watchTimer); + watchTimer.Start(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs new file mode 100644 index 0000000000..1daa5e138e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs @@ -0,0 +1,87 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace GodotTools.Internals +{ + public class BindingsGenerator : IDisposable + { + class BindingsGeneratorSafeHandle : SafeHandle + { + public BindingsGeneratorSafeHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + this.handle = handle; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + internal_Dtor(handle); + return true; + } + } + + private BindingsGeneratorSafeHandle safeHandle; + private bool disposed = false; + + public bool LogPrintEnabled + { + get => internal_LogPrintEnabled(GetPtr()); + set => internal_SetLogPrintEnabled(GetPtr(), value); + } + + public static uint Version => internal_Version(); + public static uint CsGlueVersion => internal_CsGlueVersion(); + + public Godot.Error GenerateCsApi(string outputDir) => internal_GenerateCsApi(GetPtr(), outputDir); + + internal IntPtr GetPtr() + { + if (disposed) + throw new ObjectDisposedException(GetType().FullName); + + return safeHandle.DangerousGetHandle(); + } + + public void Dispose() + { + if (disposed) + return; + + if (safeHandle != null && !safeHandle.IsInvalid) + { + safeHandle.Dispose(); + safeHandle = null; + } + + disposed = true; + } + + public BindingsGenerator() + { + safeHandle = new BindingsGeneratorSafeHandle(internal_Ctor()); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr internal_Ctor(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Dtor(IntPtr handle); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_LogPrintEnabled(IntPtr handle); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_SetLogPrintEnabled(IntPtr handle, bool enabled); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Godot.Error internal_GenerateCsApi(IntPtr handle, string outputDir); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint internal_Version(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint internal_CsGlueVersion(); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs new file mode 100644 index 0000000000..70ba7c733a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using Godot; + +namespace GodotTools.Internals +{ + public class EditorProgress : IDisposable + { + public string Task { get; } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Create(string task, string label, int amount, bool canCancel); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Dispose(string task); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_Step(string task, string state, int step, bool forceRefresh); + + public EditorProgress(string task, string label, int amount, bool canCancel = false) + { + Task = task; + internal_Create(task, label, amount, canCancel); + } + + ~EditorProgress() + { + // Should never rely on the GC to dispose EditorProgress. + // It should be disposed immediately when the task finishes. + GD.PushError("EditorProgress disposed by the Garbage Collector"); + Dispose(); + } + + public void Dispose() + { + internal_Dispose(Task); + GC.SuppressFinalize(this); + } + + public void Step(string state, int step = -1, bool forceRefresh = true) + { + internal_Step(Task, state, step, forceRefresh); + } + + public bool TryStep(string state, int step = -1, bool forceRefresh = true) + { + return internal_Step(Task, state, step, forceRefresh); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs new file mode 100644 index 0000000000..ddf3b829b5 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -0,0 +1,91 @@ +using System.Runtime.CompilerServices; + +namespace GodotTools.Internals +{ + public static class GodotSharpDirs + { + public static string ResDataDir => internal_ResDataDir(); + public static string ResMetadataDir => internal_ResMetadataDir(); + public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir(); + public static string ResAssembliesDir => internal_ResAssembliesDir(); + public static string ResConfigDir => internal_ResConfigDir(); + public static string ResTempDir => internal_ResTempDir(); + public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir(); + public static string ResTempAssembliesDir => internal_ResTempAssembliesDir(); + + public static string MonoUserDir => internal_MonoUserDir(); + public static string MonoLogsDir => internal_MonoLogsDir(); + + #region Tools-only + public static string MonoSolutionsDir => internal_MonoSolutionsDir(); + public static string BuildLogsDirs => internal_BuildLogsDirs(); + + public static string ProjectSlnPath => internal_ProjectSlnPath(); + public static string ProjectCsProjPath => internal_ProjectCsProjPath(); + + public static string DataEditorToolsDir => internal_DataEditorToolsDir(); + public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir(); + #endregion + + public static string DataMonoEtcDir => internal_DataMonoEtcDir(); + public static string DataMonoLibDir => internal_DataMonoLibDir(); + + #region Windows-only + public static string DataMonoBinDir => internal_DataMonoBinDir(); + #endregion + + + #region Internal + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResDataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResMetadataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResAssembliesDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResConfigDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempAssembliesDir(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoUserDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoLogsDir(); + + #region Tools-only + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoSolutionsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_BuildLogsDirs(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ProjectSlnPath(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ProjectCsProjPath(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataEditorToolsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataEditorPrebuiltApiDir(); + #endregion + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoEtcDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoLibDir(); + + #region Windows-only + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoBinDir(); + #endregion + + #endregion + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs new file mode 100644 index 0000000000..5c7ce832cd --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.CompilerServices; +using Godot; +using Godot.Collections; + +namespace GodotTools.Internals +{ + public static class Internal + { + public const string CSharpLanguageType = "CSharpScript"; + public const string CSharpLanguageExtension = "cs"; + + public static float EditorScale => internal_EditorScale(); + + public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) => + internal_GlobalDef(setting, defaultValue, restartIfChanged); + + public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) => + internal_EditorDef(setting, defaultValue, restartIfChanged); + + public static string FullTemplatesDir => + internal_FullTemplatesDir(); + + public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path); + + public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId); + + public static bool MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType) => + internal_MetadataIsApiAssemblyInvalidated(apiType); + + public static void MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated) => + internal_MetadataSetApiAssemblyInvalidated(apiType, invalidated); + + public static bool IsMessageQueueFlushing() => internal_IsMessageQueueFlushing(); + + public static bool GodotIs32Bits() => internal_GodotIs32Bits(); + + public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble(); + + public static void GodotMainIteration() => internal_GodotMainIteration(); + + public static ulong GetCoreApiHash() => internal_GetCoreApiHash(); + + public static ulong GetEditorApiHash() => internal_GetEditorApiHash(); + + public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded(); + + public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload); + + public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts(); + + public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => + internal_ScriptEditorEdit(resource, line, col, grabFocus); + + public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen(); + + public static Dictionary<string, object> GetScriptsMetadataOrNothing() => + internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>)); + + public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + + // Internal Calls + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern float internal_EditorScale(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_FullTemplatesDir(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_SimplifyGodotPath(this string path); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsOsxAppBundleInstalled(string bundleId); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsMessageQueueFlushing(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_GodotIs32Bits(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_GodotIsRealTDouble(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_GodotMainIteration(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern ulong internal_GetCoreApiHash(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern ulong internal_GetEditorApiHash(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsAssembliesReloadingNeeded(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ReloadAssemblies(bool softReload); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ScriptEditorDebuggerReloadScripts(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorNodeShowScriptScreen(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoWindowsInstallRoot(); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs new file mode 100644 index 0000000000..2497d276a9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Godot; +using Godot.Collections; + +namespace GodotTools.Internals +{ + public static class ScriptClassParser + { + public class ClassDecl + { + public string Name { get; } + public string Namespace { get; } + public bool Nested { get; } + public int BaseCount { get; } + + public ClassDecl(string name, string @namespace, bool nested, int baseCount) + { + Name = name; + Namespace = @namespace; + Nested = nested; + BaseCount = baseCount; + } + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes); + + public static void ParseFileOrThrow(string filePath, out IEnumerable<ClassDecl> classes) + { + var classesArray = new Array<Dictionary>(); + var error = internal_ParseFile(filePath, classesArray); + if (error != Error.Ok) + throw new Exception($"Failed to determine namespace and class for script: {filePath}. Parse error: {error}"); + + var classesList = new List<ClassDecl>(); + + foreach (var classDeclDict in classesArray) + { + classesList.Add(new ClassDecl( + (string) classDeclDict["name"], + (string) classDeclDict["namespace"], + (bool) classDeclDict["nested"], + (int) classDeclDict["base_count"] + )); + } + + classes = classesList; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs new file mode 100644 index 0000000000..300cf7fcb9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs @@ -0,0 +1,342 @@ +using Godot; +using System; +using System.IO; +using Godot.Collections; +using GodotTools.Internals; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class MonoBottomPanel : VBoxContainer + { + private EditorInterface editorInterface; + + private TabContainer panelTabs; + + private VBoxContainer panelBuildsTab; + + private ItemList buildTabsList; + private TabContainer buildTabs; + + private ToolButton warningsBtn; + private ToolButton errorsBtn; + private Button viewLogBtn; + + private void _UpdateBuildTabsList() + { + buildTabsList.Clear(); + + int currentTab = buildTabs.CurrentTab; + + bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount(); + + for (int i = 0; i < buildTabs.GetChildCount(); i++) + { + var tab = (MonoBuildTab) buildTabs.GetChild(i); + + if (tab == null) + continue; + + string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution); + itemName += " [" + tab.BuildInfo.Configuration + "]"; + + buildTabsList.AddItem(itemName, tab.IconTexture); + + string itemTooltip = "Solution: " + tab.BuildInfo.Solution; + itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration; + itemTooltip += "\nStatus: "; + + if (tab.BuildExited) + itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored"; + else + itemTooltip += "Running"; + + if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error) + itemTooltip += $"\nErrors: {tab.ErrorCount}"; + + itemTooltip += $"\nWarnings: {tab.WarningCount}"; + + buildTabsList.SetItemTooltip(i, itemTooltip); + + if (noCurrentTab || currentTab == i) + { + buildTabsList.Select(i); + _BuildTabsItemSelected(i); + } + } + } + + public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo) + { + foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren())) + { + if (buildTab.BuildInfo.Equals(buildInfo)) + return buildTab; + } + + var newBuildTab = new MonoBuildTab(buildInfo); + AddBuildTab(newBuildTab); + + return newBuildTab; + } + + private void _BuildTabsItemSelected(int idx) + { + if (idx < 0 || idx >= buildTabs.GetTabCount()) + throw new IndexOutOfRangeException(); + + buildTabs.CurrentTab = idx; + if (!buildTabs.Visible) + buildTabs.Visible = true; + + warningsBtn.Visible = true; + errorsBtn.Visible = true; + viewLogBtn.Visible = true; + } + + private void _BuildTabsNothingSelected() + { + if (buildTabs.GetTabCount() != 0) + { + // just in case + buildTabs.Visible = false; + + // This callback is called when clicking on the empty space of the list. + // ItemList won't deselect the items automatically, so we must do it ourselves. + buildTabsList.UnselectAll(); + } + + warningsBtn.Visible = false; + errorsBtn.Visible = false; + viewLogBtn.Visible = false; + } + + private void _WarningsToggled(bool pressed) + { + int currentTab = buildTabs.CurrentTab; + + if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) + throw new InvalidOperationException("No tab selected"); + + var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + buildTab.WarningsVisible = pressed; + buildTab.UpdateIssuesList(); + } + + private void _ErrorsToggled(bool pressed) + { + int currentTab = buildTabs.CurrentTab; + + if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) + throw new InvalidOperationException("No tab selected"); + + var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + buildTab.ErrorsVisible = pressed; + buildTab.UpdateIssuesList(); + } + + public void BuildProjectPressed() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); + string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); + + CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + + if (File.Exists(editorScriptsMetadataPath)) + { + try + { + File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + } + catch (IOException e) + { + GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}"); + return; + } + } + + var godotDefines = new[] + { + OS.GetName(), + Internal.GodotIs32Bits() ? "32" : "64" + }; + + bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines); + + if (!buildSuccess) + return; + + // Notify running game for hot-reload + Internal.ScriptEditorDebuggerReloadScripts(); + + // Hot-reload in the editor + GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + private void _ViewLogPressed() + { + if (!buildTabsList.IsAnythingSelected()) + return; + + var selectedItems = buildTabsList.GetSelectedItems(); + + if (selectedItems.Length != 1) + throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}"); + + int selectedItem = selectedItems[0]; + + var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem); + + OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName)); + } + + public override void _Notification(int what) + { + base._Notification(what); + + if (what == EditorSettings.NotificationEditorSettingsChanged) + { + var editorBaseControl = editorInterface.GetBaseControl(); + panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + } + } + + public void AddBuildTab(MonoBuildTab buildTab) + { + buildTabs.AddChild(buildTab); + RaiseBuildTab(buildTab); + } + + public void RaiseBuildTab(MonoBuildTab buildTab) + { + if (buildTab.GetParent() != buildTabs) + throw new InvalidOperationException("Build tab is not in the tabs list"); + + buildTabs.MoveChild(buildTab, 0); + _UpdateBuildTabsList(); + } + + public void ShowBuildTab() + { + for (int i = 0; i < panelTabs.GetTabCount(); i++) + { + if (panelTabs.GetTabControl(i) == panelBuildsTab) + { + panelTabs.CurrentTab = i; + GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this); + return; + } + } + + GD.PushError("Builds tab not found"); + } + + public override void _Ready() + { + base._Ready(); + + editorInterface = GodotSharpEditor.Instance.GetEditorInterface(); + + var editorBaseControl = editorInterface.GetBaseControl(); + + SizeFlagsVertical = (int) SizeFlags.ExpandFill; + SetAnchorsAndMarginsPreset(LayoutPreset.Wide); + + panelTabs = new TabContainer + { + TabAlign = TabContainer.TabAlignEnum.Left, + RectMinSize = new Vector2(0, 228) * Internal.EditorScale, + SizeFlagsVertical = (int) SizeFlags.ExpandFill + }; + panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + AddChild(panelTabs); + + { + // Builds tab + panelBuildsTab = new VBoxContainer + { + Name = "Builds", // TTR + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill + }; + panelTabs.AddChild(panelBuildsTab); + + var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill}; + panelBuildsTab.AddChild(toolBarHBox); + + var buildProjectBtn = new Button + { + Text = "Build Project", // TTR + FocusMode = FocusModeEnum.None + }; + buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed)); + toolBarHBox.AddChild(buildProjectBtn); + + toolBarHBox.AddSpacer(begin: false); + + warningsBtn = new ToolButton + { + Text = "Warnings", // TTR + ToggleMode = true, + Pressed = true, + Visible = false, + FocusMode = FocusModeEnum.None + }; + warningsBtn.Connect("toggled", this, nameof(_WarningsToggled)); + toolBarHBox.AddChild(warningsBtn); + + errorsBtn = new ToolButton + { + Text = "Errors", // TTR + ToggleMode = true, + Pressed = true, + Visible = false, + FocusMode = FocusModeEnum.None + }; + errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled)); + toolBarHBox.AddChild(errorsBtn); + + toolBarHBox.AddSpacer(begin: false); + + viewLogBtn = new Button + { + Text = "View log", // TTR + FocusMode = FocusModeEnum.None, + Visible = false + }; + viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed)); + toolBarHBox.AddChild(viewLogBtn); + + var hsc = new HSplitContainer + { + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill, + SizeFlagsVertical = (int) SizeFlags.ExpandFill + }; + panelBuildsTab.AddChild(hsc); + + buildTabsList = new ItemList {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill}; + buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected)); + buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected)); + hsc.AddChild(buildTabsList); + + buildTabs = new TabContainer + { + TabAlign = TabContainer.TabAlignEnum.Left, + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill, + TabsVisible = false + }; + hsc.AddChild(buildTabs); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs new file mode 100644 index 0000000000..858e852392 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs @@ -0,0 +1,47 @@ +using System; +using Godot; +using Godot.Collections; +using GodotTools.Internals; +using Path = System.IO.Path; + +namespace GodotTools +{ + [Serializable] + public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization + { + public string Solution { get; } + public string Configuration { get; } + public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization + + public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); + + public override bool Equals(object obj) + { + if (obj is MonoBuildInfo other) + return other.Solution == Solution && other.Configuration == Configuration; + + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 29 + Solution.GetHashCode(); + hash = hash * 29 + Configuration.GetHashCode(); + return hash; + } + } + + private MonoBuildInfo() + { + } + + public MonoBuildInfo(string solution, string configuration) + { + Solution = solution; + Configuration = configuration; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs new file mode 100644 index 0000000000..75fdacc0da --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs @@ -0,0 +1,260 @@ +using Godot; +using System; +using Godot.Collections; +using GodotTools.Internals; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class MonoBuildTab : VBoxContainer + { + public enum BuildResults + { + Error, + Success + } + + [Serializable] + private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization + { + public bool Warning { get; set; } + public string File { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string Code { get; set; } + public string Message { get; set; } + public string ProjectFile { get; set; } + } + + private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization + private ItemList issuesList; + + public bool BuildExited { get; private set; } = false; + + public BuildResults? BuildResult { get; private set; } = null; + + public int ErrorCount { get; private set; } = 0; + + public int WarningCount { get; private set; } = 0; + + public bool ErrorsVisible { get; set; } = true; + public bool WarningsVisible { get; set; } = true; + + public Texture IconTexture + { + get + { + if (!BuildExited) + return GetIcon("Stop", "EditorIcons"); + + if (BuildResult == BuildResults.Error) + return GetIcon("StatusError", "EditorIcons"); + + return GetIcon("StatusSuccess", "EditorIcons"); + } + } + + public MonoBuildInfo BuildInfo { get; private set; } + + private void _LoadIssuesFromFile(string csvFile) + { + using (var file = new Godot.File()) + { + Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read); + + if (openError != Error.Ok) + return; + + while (!file.EofReached()) + { + string[] csvColumns = file.GetCsvLine(); + + if (csvColumns.Length == 1 && csvColumns[0].Empty()) + return; + + if (csvColumns.Length != 7) + { + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; + } + + var issue = new BuildIssue + { + Warning = csvColumns[0] == "warning", + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6] + }; + + if (issue.Warning) + WarningCount += 1; + else + ErrorCount += 1; + + issues.Add(issue); + } + } + } + + private void _IssueActivated(int idx) + { + if (idx < 0 || idx >= issuesList.GetItemCount()) + throw new IndexOutOfRangeException("Item list index out of range"); + + // Get correct issue idx from issue list + int issueIndex = (int) issuesList.GetItemMetadata(idx); + + if (idx < 0 || idx >= issues.Count) + throw new IndexOutOfRangeException("Issue index out of range"); + + BuildIssue issue = issues[issueIndex]; + + if (issue.ProjectFile.Empty() && issue.File.Empty()) + return; + + string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir(); + + string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); + + if (!File.Exists(file)) + return; + + file = ProjectSettings.LocalizePath(file); + + if (file.StartsWith("res://")) + { + var script = (Script) ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); + + if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column)) + Internal.EditorNodeShowScriptScreen(); + } + } + + public void UpdateIssuesList() + { + issuesList.Clear(); + + using (var warningIcon = GetIcon("Warning", "EditorIcons")) + using (var errorIcon = GetIcon("Error", "EditorIcons")) + { + for (int i = 0; i < issues.Count; i++) + { + BuildIssue issue = issues[i]; + + if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) + continue; + + string tooltip = string.Empty; + tooltip += $"Message: {issue.Message}"; + + if (!issue.Code.Empty()) + tooltip += $"\nCode: {issue.Code}"; + + tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; + + string text = string.Empty; + + if (!issue.File.Empty()) + { + text += $"{issue.File}({issue.Line},{issue.Column}): "; + + tooltip += $"\nFile: {issue.File}"; + tooltip += $"\nLine: {issue.Line}"; + tooltip += $"\nColumn: {issue.Column}"; + } + + if (!issue.ProjectFile.Empty()) + tooltip += $"\nProject: {issue.ProjectFile}"; + + text += issue.Message; + + int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); + string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); + issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); + + int index = issuesList.GetItemCount() - 1; + issuesList.SetItemTooltip(index, tooltip); + issuesList.SetItemMetadata(index, i); + } + } + } + + public void OnBuildStart() + { + BuildExited = false; + + issues.Clear(); + WarningCount = 0; + ErrorCount = 0; + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void OnBuildExit(BuildResults result) + { + BuildExited = true; + BuildResult = result; + + _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName)); + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void OnBuildExecFailed(string cause) + { + BuildExited = true; + BuildResult = BuildResults.Error; + + issuesList.Clear(); + + var issue = new BuildIssue {Message = cause, Warning = false}; + + ErrorCount += 1; + issues.Add(issue); + + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void RestartBuild() + { + if (!BuildExited) + throw new InvalidOperationException("Build already started"); + + GodotSharpBuilds.RestartBuild(this); + } + + public void StopBuild() + { + if (!BuildExited) + throw new InvalidOperationException("Build is not in progress"); + + GodotSharpBuilds.StopBuild(this); + } + + public override void _Ready() + { + base._Ready(); + + issuesList = new ItemList {SizeFlagsVertical = (int) SizeFlags.ExpandFill}; + issuesList.Connect("item_activated", this, nameof(_IssueActivated)); + AddChild(issuesList); + } + + private MonoBuildTab() + { + } + + public MonoBuildTab(MonoBuildInfo buildInfo) + { + BuildInfo = buildInfo; + } + } +} diff --git a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs index fba4a8f65c..0c8d86e799 100644 --- a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs @@ -1,11 +1,11 @@ +using GodotTools.Core; using System; using System.IO; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; +using GodotTools.Internals; -namespace GodotSharpTools.Editor +namespace GodotTools { public class MonoDevelopInstance { @@ -15,24 +15,24 @@ namespace GodotSharpTools.Editor VisualStudioForMac = 1 } - readonly string solutionFile; - readonly EditorId editorId; + private readonly string solutionFile; + private readonly EditorId editorId; - Process process; + private Process process; - public void Execute(string[] files) + public void Execute(params string[] files) { bool newWindow = process == null || process.HasExited; - List<string> args = new List<string>(); + var args = new List<string>(); string command; if (Utils.OS.IsOSX()) { - string bundleId = codeEditorBundleIds[editorId]; + string bundleId = CodeEditorBundleIds[editorId]; - if (IsApplicationBundleInstalled(bundleId)) + if (Internal.IsOsxAppBundleInstalled(bundleId)) { command = "open"; @@ -47,12 +47,12 @@ namespace GodotSharpTools.Editor } else { - command = codeEditorPaths[editorId]; + command = CodeEditorPaths[editorId]; } } else { - command = codeEditorPaths[editorId]; + command = CodeEditorPaths[editorId]; } args.Add("--ipc-tcp"); @@ -72,7 +72,7 @@ namespace GodotSharpTools.Editor if (newWindow) { - process = Process.Start(new ProcessStartInfo() + process = Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), @@ -81,12 +81,12 @@ namespace GodotSharpTools.Editor } else { - Process.Start(new ProcessStartInfo() + Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), UseShellExecute = false - }); + })?.Dispose(); } } @@ -99,45 +99,42 @@ namespace GodotSharpTools.Editor this.editorId = editorId; } - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool IsApplicationBundleInstalled(string bundleId); - - static readonly IReadOnlyDictionary<EditorId, string> codeEditorPaths; - static readonly IReadOnlyDictionary<EditorId, string> codeEditorBundleIds; + private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorPaths; + private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorBundleIds; static MonoDevelopInstance() { if (Utils.OS.IsOSX()) { - codeEditorPaths = new Dictionary<EditorId, string> + CodeEditorPaths = new Dictionary<EditorId, string> { // Rely on PATH - { EditorId.MonoDevelop, "monodevelop" }, - { EditorId.VisualStudioForMac, "VisualStudio" } + {EditorId.MonoDevelop, "monodevelop"}, + {EditorId.VisualStudioForMac, "VisualStudio"} }; - codeEditorBundleIds = new Dictionary<EditorId, string> + CodeEditorBundleIds = new Dictionary<EditorId, string> { // TODO EditorId.MonoDevelop - { EditorId.VisualStudioForMac, "com.microsoft.visual-studio" } + {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} }; } else if (Utils.OS.IsWindows()) { - codeEditorPaths = new Dictionary<EditorId, string> + CodeEditorPaths = new Dictionary<EditorId, string> { // XamarinStudio is no longer a thing, and the latest version is quite old // MonoDevelop is available from source only on Windows. The recommendation // is to use Visual Studio instead. Since there are no official builds, we // will rely on custom MonoDevelop builds being added to PATH. - { EditorId.MonoDevelop, "MonoDevelop.exe" } + {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } else if (Utils.OS.IsUnix()) { - codeEditorPaths = new Dictionary<EditorId, string> + CodeEditorPaths = new Dictionary<EditorId, string> { // Rely on PATH - { EditorId.MonoDevelop, "monodevelop" } + {EditorId.MonoDevelop, "monodevelop"} }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f5fe85c722 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("GodotTools")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs new file mode 100644 index 0000000000..3ae6c10bbf --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace GodotTools.Utils +{ + public static class CollectionExtensions + { + public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null) + where T : class + { + foreach (T elem in enumerable) + { + if (predicate(elem) != null) + return elem; + } + + return orElse; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs new file mode 100644 index 0000000000..c67d48b92a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs @@ -0,0 +1,40 @@ +using System.IO; +using Godot; + +namespace GodotTools.Utils +{ + public static class Directory + { + private static string GlobalizePath(this string path) + { + return ProjectSettings.GlobalizePath(path); + } + + public static bool Exists(string path) + { + return System.IO.Directory.Exists(path.GlobalizePath()); + } + + /// Create directory recursively + public static DirectoryInfo CreateDirectory(string path) + { + return System.IO.Directory.CreateDirectory(path.GlobalizePath()); + } + + public static void Delete(string path, bool recursive) + { + System.IO.Directory.Delete(path.GlobalizePath(), recursive); + } + + + public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption) + { + return System.IO.Directory.GetDirectories(path.GlobalizePath(), searchPattern, searchOption); + } + + public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption) + { + return System.IO.Directory.GetFiles(path.GlobalizePath(), searchPattern, searchOption); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs new file mode 100644 index 0000000000..e1e2188edb --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs @@ -0,0 +1,43 @@ +using System; +using Godot; + +namespace GodotTools.Utils +{ + public static class File + { + private static string GlobalizePath(this string path) + { + return ProjectSettings.GlobalizePath(path); + } + + public static void WriteAllText(string path, string contents) + { + System.IO.File.WriteAllText(path.GlobalizePath(), contents); + } + + public static bool Exists(string path) + { + return System.IO.File.Exists(path.GlobalizePath()); + } + + public static DateTime GetLastWriteTime(string path) + { + return System.IO.File.GetLastWriteTime(path.GlobalizePath()); + } + + public static void Delete(string path) + { + System.IO.File.Delete(path.GlobalizePath()); + } + + public static void Copy(string sourceFileName, string destFileName) + { + System.IO.File.Copy(sourceFileName.GlobalizePath(), destFileName.GlobalizePath(), overwrite: true); + } + + public static byte[] ReadAllBytes(string path) + { + return System.IO.File.ReadAllBytes(path.GlobalizePath()); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs new file mode 100644 index 0000000000..e48b1115db --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace GodotTools.Utils +{ + public static class OS + { + [MethodImpl(MethodImplOptions.InternalCall)] + extern static string GetPlatformName(); + + const string HaikuName = "Haiku"; + const string OSXName = "OSX"; + const string ServerName = "Server"; + const string UWPName = "UWP"; + const string WindowsName = "Windows"; + const string X11Name = "X11"; + + public static bool IsHaiku() + { + return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsOSX() + { + return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsServer() + { + return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsUWP() + { + return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsWindows() + { + return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsX11() + { + return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + private static bool? _isUnixCache; + private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name}; + + public static bool IsUnix() + { + if (_isUnixCache.HasValue) + return _isUnixCache.Value; + + string osName = GetPlatformName(); + _isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); + return _isUnixCache.Value; + } + + public static char PathSep => IsWindows() ? ';' : ':'; + + public static string PathWhich(string name) + { + string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null; + string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); + + var searchDirs = new List<string>(); + + if (pathDirs != null) + searchDirs.AddRange(pathDirs); + + searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list + + foreach (var dir in searchDirs) + { + string path = Path.Combine(dir, name); + + if (IsWindows() && windowsExts != null) + { + foreach (var extension in windowsExts) + { + string pathWithExtension = path + extension; + + if (File.Exists(pathWithExtension)) + return pathWithExtension; + } + } + else + { + if (File.Exists(path)) + return path; + } + } + + return null; + } + + public static void RunProcess(string command, IEnumerable<string> arguments) + { + string CmdLineArgsToString(IEnumerable<string> args) + { + return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); + } + + ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + using (Process process = Process.Start(startInfo)) + { + if (process == null) + throw new Exception("No process was started"); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index fe7ced060d..45037bf637 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -38,7 +38,6 @@ #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" -#include "core/string_builder.h" #include "core/ucaps.h" #include "../glue/cs_compressed.gen.h" @@ -98,14 +97,10 @@ #define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type #define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" -#define BINDINGS_GENERATOR_VERSION UINT32_C(8) +#define BINDINGS_GENERATOR_VERSION UINT32_C(9) const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); -bool BindingsGenerator::verbose_output = false; - -BindingsGenerator *BindingsGenerator::singleton = NULL; - static String fix_doc_description(const String &p_bbcode) { // This seems to be the correct way to do this. It's the same EditorHelp does. @@ -757,47 +752,47 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { } } -void BindingsGenerator::_generate_global_constants(List<String> &p_output) { +void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { // Constants (in partial GD class) - p_output.push_back("\n#pragma warning disable CS1591 // Disable warning: " - "'Missing XML comment for publicly visible type or member'\n"); + p_output.append("\n#pragma warning disable CS1591 // Disable warning: " + "'Missing XML comment for publicly visible type or member'\n"); - p_output.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - p_output.push_back(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); + p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); for (const List<ConstantInterface>::Element *E = global_constants.front(); E; E = E->next()) { const ConstantInterface &iconstant = E->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); - Vector<String> summary_lines = xml_summary.split("\n"); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - p_output.push_back(MEMBER_BEGIN "/// <summary>\n"); + p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(summary_lines[i]); - p_output.push_back("\n"); + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - p_output.push_back(INDENT2 "/// </summary>"); + p_output.append(INDENT2 "/// </summary>"); } } - p_output.push_back(MEMBER_BEGIN "public const int "); - p_output.push_back(iconstant.proxy_name); - p_output.push_back(" = "); - p_output.push_back(itos(iconstant.value)); - p_output.push_back(";"); + p_output.append(MEMBER_BEGIN "public const int "); + p_output.append(iconstant.proxy_name); + p_output.append(" = "); + p_output.append(itos(iconstant.value)); + p_output.append(";"); } if (!global_constants.empty()) - p_output.push_back("\n"); + p_output.append("\n"); - p_output.push_back(INDENT1 CLOSE_BLOCK); // end of GD class + p_output.append(INDENT1 CLOSE_BLOCK); // end of GD class // Enums @@ -817,90 +812,82 @@ void BindingsGenerator::_generate_global_constants(List<String> &p_output) { CRASH_COND(enum_class_name != "Variant"); // Hard-coded... - if (verbose_output) { - WARN_PRINTS("Declaring global enum `" + enum_proxy_name + "` inside static class `" + enum_class_name + "`"); - } + _log("Declaring global enum `%s` inside static class `%s`\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); - p_output.push_back("\n" INDENT1 "public static partial class "); - p_output.push_back(enum_class_name); - p_output.push_back("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" INDENT1 "public static partial class "); + p_output.append(enum_class_name); + p_output.append("\n" INDENT1 OPEN_BLOCK); } - p_output.push_back("\n" INDENT1 "public enum "); - p_output.push_back(enum_proxy_name); - p_output.push_back("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" INDENT1 "public enum "); + p_output.append(enum_proxy_name); + p_output.append("\n" INDENT1 OPEN_BLOCK); for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) { const ConstantInterface &iconstant = F->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); - Vector<String> summary_lines = xml_summary.split("\n"); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - p_output.push_back(INDENT2 "/// <summary>\n"); + p_output.append(INDENT2 "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(summary_lines[i]); - p_output.push_back("\n"); + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - p_output.push_back(INDENT2 "/// </summary>\n"); + p_output.append(INDENT2 "/// </summary>\n"); } } - p_output.push_back(INDENT2); - p_output.push_back(iconstant.proxy_name); - p_output.push_back(" = "); - p_output.push_back(itos(iconstant.value)); - p_output.push_back(F != ienum.constants.back() ? ",\n" : "\n"); + p_output.append(INDENT2); + p_output.append(iconstant.proxy_name); + p_output.append(" = "); + p_output.append(itos(iconstant.value)); + p_output.append(F != ienum.constants.back() ? ",\n" : "\n"); } - p_output.push_back(INDENT1 CLOSE_BLOCK); + p_output.append(INDENT1 CLOSE_BLOCK); if (enum_in_static_class) - p_output.push_back(INDENT1 CLOSE_BLOCK); + p_output.append(INDENT1 CLOSE_BLOCK); } - p_output.push_back(CLOSE_BLOCK); // end of namespace + p_output.append(CLOSE_BLOCK); // end of namespace - p_output.push_back("\n#pragma warning restore CS1591\n"); + p_output.append("\n#pragma warning restore CS1591\n"); } -Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { - - verbose_output = p_verbose_output; - - String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME); +Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(proj_dir)) { - Error err = da->make_dir_recursive(proj_dir); + if (!DirAccess::exists(p_proj_dir)) { + Error err = da->make_dir_recursive(p_proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(proj_dir); + da->change_dir(p_proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(proj_dir, "Core"); - String obj_type_dir = path_join(proj_dir, "ObjectType"); - - Vector<String> compile_items; + String core_dir = path::join(p_proj_dir, "Core"); + String obj_type_dir = path::join(p_proj_dir, "ObjectType"); // Generate source file for global scope constants and enums { - List<String> constants_source; + StringBuilder constants_source; _generate_global_constants(constants_source); - String output_file = path_join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs"); + String output_file = path::join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs"); Error save_err = _save_file(output_file, constants_source); if (save_err != OK) return save_err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { @@ -909,7 +896,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, if (itype.api_type == ClassDB::API_EDITOR) continue; - String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs"); + String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs"); Error err = _generate_cs_type(itype, output_file); if (err == ERR_SKIP) @@ -918,19 +905,19 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, if (err != OK) return err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } // Generate sources from compressed files - Map<String, CompressedFile> compressed_files; + Map<String, GodotCsCompressedFile> compressed_files; get_compressed_files(compressed_files); - for (Map<String, CompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) { + for (Map<String, GodotCsCompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) { const String &file_name = E->key(); - const CompressedFile &file_data = E->value(); + const GodotCsCompressedFile &file_data = E->value(); - String output_file = path_join(core_dir, file_name); + String output_file = path::join(core_dir, file_name); Vector<uint8_t> data; data.resize(file_data.uncompressed_size); @@ -948,31 +935,31 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, file->store_buffer(data.ptr(), data.size()); file->close(); - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } - List<String> cs_icalls_content; - - cs_icalls_content.push_back("using System;\n" - "using System.Runtime.CompilerServices;\n" - "\n"); - cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); - - cs_icalls_content.push_back(MEMBER_BEGIN "internal static ulong godot_api_hash = "); - cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); - cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint bindings_version = "); - cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint cs_glue_version = "); - cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n"); - -#define ADD_INTERNAL_CALL(m_icall) \ - if (!m_icall.editor_only) { \ - cs_icalls_content.push_back(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.push_back(INDENT2 "internal extern static "); \ - cs_icalls_content.push_back(m_icall.im_type_out + " "); \ - cs_icalls_content.push_back(m_icall.name + "("); \ - cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ + StringBuilder cs_icalls_content; + + cs_icalls_content.append("using System;\n" + "using System.Runtime.CompilerServices;\n" + "\n"); + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); + + cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = "); + cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); + cs_icalls_content.append(MEMBER_BEGIN "internal static uint bindings_version = "); + cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); + cs_icalls_content.append(MEMBER_BEGIN "internal static uint cs_glue_version = "); + cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + +#define ADD_INTERNAL_CALL(m_icall) \ + if (!m_icall.editor_only) { \ + cs_icalls_content.append(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ + cs_icalls_content.append(INDENT2 "internal extern static "); \ + cs_icalls_content.append(m_icall.im_type_out + " "); \ + cs_icalls_content.append(m_icall.name + "("); \ + cs_icalls_content.append(m_icall.im_sig + ");\n"); \ } for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next()) @@ -982,54 +969,35 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, #undef ADD_INTERNAL_CALL - cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); - String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); + String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); Error err = _save_file(internal_methods_file, cs_icalls_content); if (err != OK) return err; - compile_items.push_back(internal_methods_file); - - String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items); - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = String(CORE_API_ASSEMBLY_NAME).plus_file(CORE_API_ASSEMBLY_NAME ".csproj"); - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - - r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info); - - if (verbose_output) - OS::get_singleton()->print("The solution and C# project for the Core API was generated successfully\n"); + r_compile_items.push_back(internal_methods_file); return OK; } -Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { - - verbose_output = p_verbose_output; - - String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); +Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(proj_dir)) { - Error err = da->make_dir_recursive(proj_dir); + if (!DirAccess::exists(p_proj_dir)) { + Error err = da->make_dir_recursive(p_proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(proj_dir); + da->change_dir(p_proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(proj_dir, "Core"); - String obj_type_dir = path_join(proj_dir, "ObjectType"); - - Vector<String> compile_items; + String core_dir = path::join(p_proj_dir, "Core"); + String obj_type_dir = path::join(p_proj_dir, "ObjectType"); for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { const TypeInterface &itype = E.get(); @@ -1037,7 +1005,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir if (itype.api_type != ClassDB::API_EDITOR) continue; - String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs"); + String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs"); Error err = _generate_cs_type(itype, output_file); if (err == ERR_SKIP) @@ -1046,32 +1014,32 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir if (err != OK) return err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } - List<String> cs_icalls_content; - - cs_icalls_content.push_back("using System;\n" - "using System.Runtime.CompilerServices;\n" - "\n"); - cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); - - cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = "); - cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n"); - cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = "); - cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = "); - cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n"); - cs_icalls_content.push_back("\n"); - -#define ADD_INTERNAL_CALL(m_icall) \ - if (m_icall.editor_only) { \ - cs_icalls_content.push_back(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.push_back(INDENT2 "internal extern static "); \ - cs_icalls_content.push_back(m_icall.im_type_out + " "); \ - cs_icalls_content.push_back(m_icall.name + "("); \ - cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ + StringBuilder cs_icalls_content; + + cs_icalls_content.append("using System;\n" + "using System.Runtime.CompilerServices;\n" + "\n"); + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); + + cs_icalls_content.append(INDENT2 "internal static ulong godot_api_hash = "); + cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n"); + cs_icalls_content.append(INDENT2 "internal static uint bindings_version = "); + cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); + cs_icalls_content.append(INDENT2 "internal static uint cs_glue_version = "); + cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + cs_icalls_content.append("\n"); + +#define ADD_INTERNAL_CALL(m_icall) \ + if (m_icall.editor_only) { \ + cs_icalls_content.append(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ + cs_icalls_content.append(INDENT2 "internal extern static "); \ + cs_icalls_content.append(m_icall.im_type_out + " "); \ + cs_icalls_content.append(m_icall.name + "("); \ + cs_icalls_content.append(m_icall.im_sig + ");\n"); \ } for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) @@ -1081,67 +1049,64 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir #undef ADD_INTERNAL_CALL - cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); - String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); + String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); Error err = _save_file(internal_methods_file, cs_icalls_content); if (err != OK) return err; - compile_items.push_back(internal_methods_file); - - String guid = CSharpProject::generate_editor_api_project(proj_dir, "../" CORE_API_ASSEMBLY_NAME "/" CORE_API_ASSEMBLY_NAME ".csproj", compile_items); - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = String(EDITOR_API_ASSEMBLY_NAME).plus_file(EDITOR_API_ASSEMBLY_NAME ".csproj"); - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - - r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info); - - if (verbose_output) - OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n"); + r_compile_items.push_back(internal_methods_file); return OK; } -Error BindingsGenerator::generate_cs_api(const String &p_output_dir, bool p_verbose_output) { +Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { + + String output_dir = path::abspath(path::realpath(p_output_dir)); DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(p_output_dir)) { - Error err = da->make_dir_recursive(p_output_dir); + if (!DirAccess::exists(output_dir)) { + Error err = da->make_dir_recursive(output_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - DotNetSolution solution(API_SOLUTION_NAME); + Error proj_err; - if (!solution.set_path(p_output_dir)) - return ERR_FILE_NOT_FOUND; + // Generate GodotSharp source files - Error proj_err; + String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME); + Vector<String> core_compile_items; - proj_err = generate_cs_core_project(p_output_dir, solution, p_verbose_output); + proj_err = generate_cs_core_project(core_proj_dir, core_compile_items); if (proj_err != OK) { ERR_PRINT("Generation of the Core API C# project failed"); return proj_err; } - proj_err = generate_cs_editor_project(p_output_dir, solution, p_verbose_output); + // Generate GodotSharpEditor source files + + String editor_proj_dir = output_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); + Vector<String> editor_compile_items; + + proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items); if (proj_err != OK) { ERR_PRINT("Generation of the Editor API C# project failed"); return proj_err; } - Error sln_error = solution.save(); - if (sln_error != OK) { - ERR_PRINT("Failed to save API solution"); - return sln_error; + // Generate solution + + if (!CSharpProject::generate_api_solution(output_dir, + core_proj_dir, core_compile_items, editor_proj_dir, editor_compile_items)) { + return ERR_CANT_CREATE; } + _log("The solution for the Godot API was generated successfully\n"); + return OK; } @@ -1169,65 +1134,64 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; - if (verbose_output) - OS::get_singleton()->print("Generating %s.cs...\n", itype.proxy_name.utf8().get_data()); + _log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data()); String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types - List<String> output; + StringBuilder output; - output.push_back("using System;\n"); // IntPtr - output.push_back("using System.Diagnostics;\n"); // DebuggerBrowsable + output.append("using System;\n"); // IntPtr + output.append("using System.Diagnostics;\n"); // DebuggerBrowsable - output.push_back("\n" - "#pragma warning disable CS1591 // Disable warning: " - "'Missing XML comment for publicly visible type or member'\n" - "#pragma warning disable CS1573 // Disable warning: " - "'Parameter has no matching param tag in the XML comment'\n"); + output.append("\n" + "#pragma warning disable CS1591 // Disable warning: " + "'Missing XML comment for publicly visible type or member'\n" + "#pragma warning disable CS1573 // Disable warning: " + "'Parameter has no matching param tag in the XML comment'\n"); - output.push_back("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + output.append("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); const DocData::ClassDoc *class_doc = itype.class_doc; if (class_doc && class_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(class_doc->description), &itype); - Vector<String> summary_lines = xml_summary.split("\n"); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - output.push_back(INDENT1 "/// <summary>\n"); + output.append(INDENT1 "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.push_back(INDENT1 "/// "); - output.push_back(summary_lines[i]); - output.push_back("\n"); + output.append(INDENT1 "/// "); + output.append(summary_lines[i]); + output.append("\n"); } - output.push_back(INDENT1 "/// </summary>\n"); + output.append(INDENT1 "/// </summary>\n"); } } - output.push_back(INDENT1 "public "); + output.append(INDENT1 "public "); if (itype.is_singleton) { - output.push_back("static partial class "); + output.append("static partial class "); } else { - output.push_back(itype.is_instantiable ? "partial class " : "abstract partial class "); + output.append(itype.is_instantiable ? "partial class " : "abstract partial class "); } - output.push_back(itype.proxy_name); + output.append(itype.proxy_name); if (itype.is_singleton) { - output.push_back("\n"); + output.append("\n"); } else if (is_derived_type) { if (obj_types.has(itype.base_name)) { - output.push_back(" : "); - output.push_back(obj_types[itype.base_name].proxy_name); - output.push_back("\n"); + output.append(" : "); + output.append(obj_types[itype.base_name].proxy_name); + output.append("\n"); } else { ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class " + itype.name); return ERR_INVALID_DATA; } } - output.push_back(INDENT1 "{"); + output.append(INDENT1 "{"); if (class_doc) { @@ -1238,30 +1202,30 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str if (iconstant.const_doc && iconstant.const_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype); - Vector<String> summary_lines = xml_summary.split("\n"); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - output.push_back(MEMBER_BEGIN "/// <summary>\n"); + output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.push_back(INDENT2 "/// "); - output.push_back(summary_lines[i]); - output.push_back("\n"); + output.append(INDENT2 "/// "); + output.append(summary_lines[i]); + output.append("\n"); } - output.push_back(INDENT2 "/// </summary>"); + output.append(INDENT2 "/// </summary>"); } } - output.push_back(MEMBER_BEGIN "public const int "); - output.push_back(iconstant.proxy_name); - output.push_back(" = "); - output.push_back(itos(iconstant.value)); - output.push_back(";"); + output.append(MEMBER_BEGIN "public const int "); + output.append(iconstant.proxy_name); + output.append(" = "); + output.append(itos(iconstant.value)); + output.append(";"); } if (itype.constants.size()) - output.push_back("\n"); + output.append("\n"); // Add enums @@ -1270,38 +1234,38 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str ERR_FAIL_COND_V(ienum.constants.empty(), ERR_BUG); - output.push_back(MEMBER_BEGIN "public enum "); - output.push_back(ienum.cname.operator String()); - output.push_back(MEMBER_BEGIN OPEN_BLOCK); + output.append(MEMBER_BEGIN "public enum "); + output.append(ienum.cname.operator String()); + output.append(MEMBER_BEGIN OPEN_BLOCK); for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) { const ConstantInterface &iconstant = F->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype); - Vector<String> summary_lines = xml_summary.split("\n"); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - output.push_back(INDENT3 "/// <summary>\n"); + output.append(INDENT3 "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.push_back(INDENT3 "/// "); - output.push_back(summary_lines[i]); - output.push_back("\n"); + output.append(INDENT3 "/// "); + output.append(summary_lines[i]); + output.append("\n"); } - output.push_back(INDENT3 "/// </summary>\n"); + output.append(INDENT3 "/// </summary>\n"); } } - output.push_back(INDENT3); - output.push_back(iconstant.proxy_name); - output.push_back(" = "); - output.push_back(itos(iconstant.value)); - output.push_back(F != ienum.constants.back() ? ",\n" : "\n"); + output.append(INDENT3); + output.append(iconstant.proxy_name); + output.append(" = "); + output.append(itos(iconstant.value)); + output.append(F != ienum.constants.back() ? ",\n" : "\n"); } - output.push_back(INDENT2 CLOSE_BLOCK); + output.append(INDENT2 CLOSE_BLOCK); } // Add properties @@ -1322,53 +1286,54 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str if (itype.is_singleton) { // Add the type name and the singleton pointer as static fields - output.push_back(MEMBER_BEGIN "private static Godot.Object singleton;\n"); - output.push_back(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 - "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 - "singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4 - "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); - - output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); - output.push_back(itype.name); - output.push_back("\";\n"); - - output.push_back(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); - output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.push_back("." ICALL_PREFIX); - output.push_back(itype.name); - output.push_back(SINGLETON_ICALL_SUFFIX "();\n"); + output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n"); + output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 + "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 + "singleton = Engine.GetSingleton(typeof("); + output.append(itype.proxy_name); + output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); + + output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + output.append(itype.name); + output.append("\";\n"); + + output.append(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); + output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); + output.append("." ICALL_PREFIX); + output.append(itype.name); + output.append(SINGLETON_ICALL_SUFFIX "();\n"); } else if (is_derived_type) { // Add member fields - output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); - output.push_back(itype.name); - output.push_back("\";\n"); + output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + output.append(itype.name); + output.append("\";\n"); // Add default constructor if (itype.is_instantiable) { - output.push_back(MEMBER_BEGIN "public "); - output.push_back(itype.proxy_name); - output.push_back("() : this("); - output.push_back(itype.memory_own ? "true" : "false"); + output.append(MEMBER_BEGIN "public "); + output.append(itype.proxy_name); + output.append("() : this("); + output.append(itype.memory_own ? "true" : "false"); // The default constructor may also be called by the engine when instancing existing native objects // The engine will initialize the pointer field of the managed side before calling the constructor // This is why we only allocate a new native object from the constructor if the pointer field is not set - output.push_back(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); - output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.push_back("." + ctor_method); - output.push_back("(this);\n" CLOSE_BLOCK_L2); + output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); + output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); + output.append("." + ctor_method); + output.append("(this);\n" CLOSE_BLOCK_L2); } else { // Hide the constructor - output.push_back(MEMBER_BEGIN "internal "); - output.push_back(itype.proxy_name); - output.push_back("() {}\n"); + output.append(MEMBER_BEGIN "internal "); + output.append(itype.proxy_name); + output.append("() {}\n"); } // Add.. em.. trick constructor. Sort of. - output.push_back(MEMBER_BEGIN "internal "); - output.push_back(itype.proxy_name); - output.push_back("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); + output.append(MEMBER_BEGIN "internal "); + output.append(itype.proxy_name); + output.append("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); } int method_bind_count = 0; @@ -1395,17 +1360,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str custom_icalls.push_back(ctor_icall); } - output.push_back(INDENT1 CLOSE_BLOCK /* class */ + output.append(INDENT1 CLOSE_BLOCK /* class */ CLOSE_BLOCK /* namespace */); - output.push_back("\n" - "#pragma warning restore CS1591\n" - "#pragma warning restore CS1573\n"); + output.append("\n" + "#pragma warning restore CS1591\n" + "#pragma warning restore CS1573\n"); return _save_file(p_output_file, output); } -Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, List<String> &p_output) { +Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output) { const MethodInterface *setter = p_itype.find_method_by_name(p_iprop.setter); @@ -1452,72 +1417,94 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype); - Vector<String> summary_lines = xml_summary.split("\n"); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - p_output.push_back(MEMBER_BEGIN "/// <summary>\n"); + p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(summary_lines[i]); - p_output.push_back("\n"); + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - p_output.push_back(INDENT2 "/// </summary>"); + p_output.append(INDENT2 "/// </summary>"); } } - p_output.push_back(MEMBER_BEGIN "public "); + p_output.append(MEMBER_BEGIN "public "); if (p_itype.is_singleton) - p_output.push_back("static "); + p_output.append("static "); - p_output.push_back(prop_itype->cs_type); - p_output.push_back(" "); - p_output.push_back(p_iprop.proxy_name); - p_output.push_back("\n" INDENT2 OPEN_BLOCK); + p_output.append(prop_itype->cs_type); + p_output.append(" "); + p_output.append(p_iprop.proxy_name); + p_output.append("\n" INDENT2 OPEN_BLOCK); if (getter) { - p_output.push_back(INDENT3 "get\n" OPEN_BLOCK_L3); - p_output.push_back("return "); - p_output.push_back(getter->proxy_name + "("); + p_output.append(INDENT3 "get\n" + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning disable CS0618 // Disable warning about obsolete method\n" + + OPEN_BLOCK_L3); + + p_output.append("return "); + p_output.append(getter->proxy_name + "("); if (p_iprop.index != -1) { const ArgumentInterface &idx_arg = getter->arguments.front()->get(); if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == NULL); - p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); + p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); } else { - p_output.push_back(itos(p_iprop.index)); + p_output.append(itos(p_iprop.index)); } } - p_output.push_back(");\n" CLOSE_BLOCK_L3); + p_output.append(");\n" + + CLOSE_BLOCK_L3 + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning restore CS0618\n"); } if (setter) { - p_output.push_back(INDENT3 "set\n" OPEN_BLOCK_L3); - p_output.push_back(setter->proxy_name + "("); + p_output.append(INDENT3 "set\n" + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning disable CS0618 // Disable warning about obsolete method\n" + + OPEN_BLOCK_L3); + + p_output.append(setter->proxy_name + "("); if (p_iprop.index != -1) { const ArgumentInterface &idx_arg = setter->arguments.front()->get(); if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == NULL); - p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); + p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); } else { - p_output.push_back(itos(p_iprop.index) + ", "); + p_output.append(itos(p_iprop.index) + ", "); } } - p_output.push_back("value);\n" CLOSE_BLOCK_L3); + p_output.append("value);\n" + + CLOSE_BLOCK_L3 + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning restore CS0618\n"); } - p_output.push_back(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L2); return OK; } -Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, List<String> &p_output) { +Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) { const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); @@ -1529,7 +1516,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf String icall_params = method_bind_field + ", "; icall_params += sformat(p_itype.cs_in, "this"); - List<String> default_args_doc; + StringBuilder default_args_doc; // Retrieve information from the arguments for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) { @@ -1598,7 +1585,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Apparently the name attribute must not include the @ String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name; - default_args_doc.push_back(INDENT2 "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is " + def_arg + "</param>\n"); + default_args_doc.append(INDENT2 "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is " + def_arg + "</param>\n"); } else { icall_params += arg_type->cs_in.empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name); } @@ -1607,61 +1594,67 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { - p_output.push_back(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr "); - p_output.push_back(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); - p_output.push_back(p_imethod.name); - p_output.push_back("\");\n"); + p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr "); + p_output.append(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); + p_output.append(p_imethod.name); + p_output.append("\");\n"); } if (p_imethod.method_doc && p_imethod.method_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(p_imethod.method_doc->description), &p_itype); - Vector<String> summary_lines = xml_summary.split("\n"); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - if (summary_lines.size() || default_args_doc.size()) { - p_output.push_back(MEMBER_BEGIN "/// <summary>\n"); + if (summary_lines.size() || default_args_doc.get_string_length()) { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(summary_lines[i]); - p_output.push_back("\n"); - } - - for (List<String>::Element *E = default_args_doc.front(); E; E = E->next()) { - p_output.push_back(E->get()); + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - p_output.push_back(INDENT2 "/// </summary>"); + p_output.append(default_args_doc.as_string()); + p_output.append(INDENT2 "/// </summary>"); } } if (!p_imethod.is_internal) { - p_output.push_back(MEMBER_BEGIN "[GodotMethod(\""); - p_output.push_back(p_imethod.name); - p_output.push_back("\")]"); + p_output.append(MEMBER_BEGIN "[GodotMethod(\""); + p_output.append(p_imethod.name); + p_output.append("\")]"); } - p_output.push_back(MEMBER_BEGIN); - p_output.push_back(p_imethod.is_internal ? "internal " : "public "); + if (p_imethod.is_deprecated) { + if (p_imethod.deprecation_message.empty()) + WARN_PRINTS("An empty deprecation message is discouraged. Method: " + p_imethod.proxy_name); + + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(p_imethod.deprecation_message); + p_output.append("\")]"); + } + + p_output.append(MEMBER_BEGIN); + p_output.append(p_imethod.is_internal ? "internal " : "public "); if (p_itype.is_singleton) { - p_output.push_back("static "); + p_output.append("static "); } else if (p_imethod.is_virtual) { - p_output.push_back("virtual "); + p_output.append("virtual "); } - p_output.push_back(return_type->cs_type + " "); - p_output.push_back(p_imethod.proxy_name + "("); - p_output.push_back(arguments_sig + ")\n" OPEN_BLOCK_L2); + p_output.append(return_type->cs_type + " "); + p_output.append(p_imethod.proxy_name + "("); + p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L2); if (p_imethod.is_virtual) { // Godot virtual method must be overridden, therefore we return a default value by default. if (return_type->cname == name_cache.type_void) { - p_output.push_back("return;\n" CLOSE_BLOCK_L2); + p_output.append("return;\n" CLOSE_BLOCK_L2); } else { - p_output.push_back("return default("); - p_output.push_back(return_type->cs_type); - p_output.push_back(");\n" CLOSE_BLOCK_L2); + p_output.append("return default("); + p_output.append(return_type->cs_type); + p_output.append(");\n" CLOSE_BLOCK_L2); } return OK; // Won't increment method bind count @@ -1670,16 +1663,16 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (p_imethod.requires_object_call) { // Fallback to Godot's object.Call(string, params) - p_output.push_back(CS_METHOD_CALL "(\""); - p_output.push_back(p_imethod.name); - p_output.push_back("\""); + p_output.append(CS_METHOD_CALL "(\""); + p_output.append(p_imethod.name); + p_output.append("\""); for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) { - p_output.push_back(", "); - p_output.push_back(F->get().name); + p_output.append(", "); + p_output.append(F->get().name); } - p_output.push_back(");\n" CLOSE_BLOCK_L2); + p_output.append(");\n" CLOSE_BLOCK_L2); return OK; // Won't increment method bind count } @@ -1693,37 +1686,36 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf im_call += "." + im_icall->name + "(" + icall_params + ")"; if (p_imethod.arguments.size()) - p_output.push_back(cs_in_statements); + p_output.append(cs_in_statements); if (return_type->cname == name_cache.type_void) { - p_output.push_back(im_call + ";\n"); + p_output.append(im_call + ";\n"); } else if (return_type->cs_out.empty()) { - p_output.push_back("return " + im_call + ";\n"); + p_output.append("return " + im_call + ";\n"); } else { - p_output.push_back(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); - p_output.push_back("\n"); + p_output.append(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); + p_output.append("\n"); } - p_output.push_back(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L2); } p_method_bind_count++; + return OK; } Error BindingsGenerator::generate_glue(const String &p_output_dir) { - verbose_output = true; - bool dir_exists = DirAccess::exists(p_output_dir); ERR_EXPLAIN("The output directory does not exist."); ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); - List<String> output; + StringBuilder output; - output.push_back("/* THIS FILE IS GENERATED DO NOT EDIT */\n"); - output.push_back("#include \"" GLUE_HEADER_FILE "\"\n"); - output.push_back("\n#ifdef MONO_GLUE_ENABLED\n"); + output.append("/* THIS FILE IS GENERATED DO NOT EDIT */\n"); + output.append("#include \"" GLUE_HEADER_FILE "\"\n"); + output.append("\n#ifdef MONO_GLUE_ENABLED\n"); generated_icall_funcs.clear(); @@ -1763,11 +1755,11 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (!find_icall_by_name(singleton_icall.name, custom_icalls)) custom_icalls.push_back(singleton_icall); - output.push_back("Object* "); - output.push_back(singleton_icall_name); - output.push_back("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\""); - output.push_back(itype.proxy_name); - output.push_back("\");\n" CLOSE_BLOCK "\n"); + output.append("Object* "); + output.append(singleton_icall_name); + output.append("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\""); + output.append(itype.proxy_name); + output.append("\");\n" CLOSE_BLOCK "\n"); } if (is_derived_type && itype.is_instantiable) { @@ -1776,43 +1768,43 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (!find_icall_by_name(ctor_icall.name, custom_icalls)) custom_icalls.push_back(ctor_icall); - output.push_back("Object* "); - output.push_back(ctor_method); - output.push_back("(MonoObject* obj) " OPEN_BLOCK - "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \""); - output.push_back(itype.name); - output.push_back("\");\n" - "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n" - "\treturn instance;\n" CLOSE_BLOCK "\n"); + output.append("Object* "); + output.append(ctor_method); + output.append("(MonoObject* obj) " OPEN_BLOCK + "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \""); + output.append(itype.name); + output.append("\");\n" + "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n" + "\treturn instance;\n" CLOSE_BLOCK "\n"); } } - output.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK "\n"); + output.append("namespace GodotSharpBindings\n" OPEN_BLOCK "\n"); - output.push_back("uint64_t get_core_api_hash() { return "); - output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n"); + output.append("uint64_t get_core_api_hash() { return "); + output.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n"); - output.push_back("#ifdef TOOLS_ENABLED\n" - "uint64_t get_editor_api_hash() { return "); - output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n"); - output.push_back("#endif // TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n" + "uint64_t get_editor_api_hash() { return "); + output.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n"); + output.append("#endif // TOOLS_ENABLED\n"); - output.push_back("uint32_t get_bindings_version() { return "); - output.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); + output.append("uint32_t get_bindings_version() { return "); + output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); - output.push_back("\nvoid register_generated_icalls() " OPEN_BLOCK); - output.push_back("\tgodot_register_glue_header_icalls();\n"); + output.append("\nvoid register_generated_icalls() " OPEN_BLOCK); + output.append("\tgodot_register_glue_header_icalls();\n"); -#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ - { \ - output.push_back("\tmono_add_internal_call("); \ - output.push_back("\"" BINDINGS_NAMESPACE "."); \ - output.push_back(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ - output.push_back("::"); \ - output.push_back(m_icall.name); \ - output.push_back("\", (void*)"); \ - output.push_back(m_icall.name); \ - output.push_back(");\n"); \ +#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ + { \ + output.append("\tmono_add_internal_call("); \ + output.append("\"" BINDINGS_NAMESPACE "."); \ + output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ + output.append("::"); \ + output.append(m_icall.name); \ + output.append("\", (void*)"); \ + output.append(m_icall.name); \ + output.append(");\n"); \ } bool tools_sequence = false; @@ -1821,11 +1813,11 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (tools_sequence) { if (!E->get().editor_only) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } } else { if (E->get().editor_only) { - output.push_back("#ifdef TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n"); tools_sequence = true; } } @@ -1835,23 +1827,23 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (tools_sequence) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } - output.push_back("#ifdef TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n"); for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) ADD_INTERNAL_CALL_REGISTRATION(E->get()); - output.push_back("#endif // TOOLS_ENABLED\n"); + output.append("#endif // TOOLS_ENABLED\n"); for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) { if (tools_sequence) { if (!E->get().editor_only) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } } else { if (E->get().editor_only) { - output.push_back("#ifdef TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n"); tools_sequence = true; } } @@ -1861,16 +1853,16 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (tools_sequence) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } #undef ADD_INTERNAL_CALL_REGISTRATION - output.push_back(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n"); + output.append(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n"); - output.push_back("\n#endif // MONO_GLUE_ENABLED\n"); + output.append("\n#endif // MONO_GLUE_ENABLED\n"); - Error save_err = _save_file(path_join(p_output_dir, "mono_glue.gen.cpp"), output); + Error save_err = _save_file(path::join(p_output_dir, "mono_glue.gen.cpp"), output); if (save_err != OK) return save_err; @@ -1883,23 +1875,20 @@ uint32_t BindingsGenerator::get_version() { return BINDINGS_GENERATOR_VERSION; } -Error BindingsGenerator::_save_file(const String &p_path, const List<String> &p_content) { +Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) { FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE); ERR_EXPLAIN("Cannot open file: " + p_path); ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); - for (const List<String>::Element *E = p_content.front(); E; E = E->next()) { - file->store_string(E->get()); - } - + file->store_string(p_content.as_string()); file->close(); return OK; } -Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, List<String> &p_output) { +Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, StringBuilder &p_output) { if (p_imethod.is_virtual) return OK; // Ignore @@ -1955,15 +1944,15 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte generated_icall_funcs.push_back(im_icall); if (im_icall->editor_only) - p_output.push_back("#ifdef TOOLS_ENABLED\n"); + p_output.append("#ifdef TOOLS_ENABLED\n"); // Generate icall function - p_output.push_back(ret_void ? "void " : return_type->c_type_out + " "); - p_output.push_back(icall_method); - p_output.push_back("("); - p_output.push_back(c_func_sig); - p_output.push_back(") " OPEN_BLOCK); + p_output.append(ret_void ? "void " : return_type->c_type_out + " "); + p_output.append(icall_method); + p_output.append("("); + p_output.append(c_func_sig); + p_output.append(") " OPEN_BLOCK); String fail_ret = ret_void ? "" : ", " + (return_type->c_type_out.ends_with("*") ? "NULL" : return_type->c_type_out + "()"); @@ -1977,7 +1966,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte // the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr, // it could be deleted too early. This is the case with GDScript.new() which returns OBJECT. // Alternatively, we could just return Variant, but that would result in a worse API. - p_output.push_back("\tVariant " C_LOCAL_VARARG_RET ";\n"); + p_output.append("\tVariant " C_LOCAL_VARARG_RET ";\n"); } if (return_type->is_object_type) { @@ -1987,83 +1976,82 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte ptrcall_return_type = return_type->c_type; } - p_output.push_back("\t" + ptrcall_return_type); - p_output.push_back(" " C_LOCAL_RET); - p_output.push_back(initialization + ";\n"); - p_output.push_back("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE); - p_output.push_back(fail_ret); - p_output.push_back(");\n"); + p_output.append("\t" + ptrcall_return_type); + p_output.append(" " C_LOCAL_RET); + p_output.append(initialization + ";\n"); + p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE); + p_output.append(fail_ret); + p_output.append(");\n"); } else { - p_output.push_back("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); + p_output.append("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); } if (p_imethod.arguments.size()) { if (p_imethod.is_vararg) { - String err_fail_macro = ret_void ? "ERR_FAIL_COND" : "ERR_FAIL_COND_V"; String vararg_arg = "arg" + argc_str; String real_argc_str = itos(p_imethod.arguments.size() - 1); // Arguments count without vararg - p_output.push_back("\tint vararg_length = mono_array_length("); - p_output.push_back(vararg_arg); - p_output.push_back(");\n\tint total_length = "); - p_output.push_back(real_argc_str); - p_output.push_back(" + vararg_length;\n" - "\tArgumentsVector<Variant> varargs(vararg_length);\n" - "\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n"); - p_output.push_back(c_in_statements); - p_output.push_back("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK - "\t\tMonoObject* elem = mono_array_get("); - p_output.push_back(vararg_arg); - p_output.push_back(", MonoObject*, i);\n" - "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" - "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); - p_output.push_back(real_argc_str); - p_output.push_back(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK); + p_output.append("\tint vararg_length = mono_array_length("); + p_output.append(vararg_arg); + p_output.append(");\n\tint total_length = "); + p_output.append(real_argc_str); + p_output.append(" + vararg_length;\n" + "\tArgumentsVector<Variant> varargs(vararg_length);\n" + "\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n"); + p_output.append(c_in_statements); + p_output.append("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK + "\t\tMonoObject* elem = mono_array_get("); + p_output.append(vararg_arg); + p_output.append(", MonoObject*, i);\n" + "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" + "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); + p_output.append(real_argc_str); + p_output.append(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK); } else { - p_output.push_back(c_in_statements); - p_output.push_back("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); - p_output.push_back(argc_str + "] = { "); - p_output.push_back(c_args_var_content + " };\n"); + p_output.append(c_in_statements); + p_output.append("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); + p_output.append(argc_str + "] = { "); + p_output.append(c_args_var_content + " };\n"); } } if (p_imethod.is_vararg) { - p_output.push_back("\tVariant::CallError vcall_error;\n\t"); + p_output.append("\tVariant::CallError vcall_error;\n\t"); if (!ret_void) { // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.push_back(C_LOCAL_VARARG_RET " = "); + p_output.append(C_LOCAL_VARARG_RET " = "); } else { - p_output.push_back(C_LOCAL_RET " = "); + p_output.append(C_LOCAL_RET " = "); } } - p_output.push_back(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); - p_output.push_back(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); - p_output.push_back(", total_length, vcall_error);\n"); + p_output.append(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); + p_output.append(", total_length, vcall_error);\n"); // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.push_back("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"); + p_output.append("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"); } } else { - p_output.push_back("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); - p_output.push_back(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); - p_output.push_back(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n"); + p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); + p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n"); } if (!ret_void) { if (return_type->c_out.empty()) - p_output.push_back("\treturn " C_LOCAL_RET ";\n"); + p_output.append("\treturn " C_LOCAL_RET ";\n"); else - p_output.push_back(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); + p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); } - p_output.push_back(CLOSE_BLOCK "\n"); + p_output.append(CLOSE_BLOCK "\n"); if (im_icall->editor_only) - p_output.push_back("#endif // TOOLS_ENABLED\n"); + p_output.append("#endif // TOOLS_ENABLED\n"); } return OK; @@ -2116,6 +2104,58 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol return &placeholder_types.insert(placeholder.cname, placeholder)->get(); } +StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta) { + + switch (p_meta) { + case GodotTypeInfo::METADATA_INT_IS_INT8: + return "sbyte"; + break; + case GodotTypeInfo::METADATA_INT_IS_INT16: + return "short"; + break; + case GodotTypeInfo::METADATA_INT_IS_INT32: + return "int"; + break; + case GodotTypeInfo::METADATA_INT_IS_INT64: + return "long"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT8: + return "byte"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT16: + return "ushort"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT32: + return "uint"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT64: + return "ulong"; + break; + default: + // Assume INT32 + return "int"; + } +} + +StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta) { + + switch (p_meta) { + case GodotTypeInfo::METADATA_REAL_IS_FLOAT: + return "float"; + break; + case GodotTypeInfo::METADATA_REAL_IS_DOUBLE: + return "double"; + break; + default: + // Assume real_t (float or double depending of REAL_T_IS_DOUBLE) +#ifdef REAL_T_IS_DOUBLE + return "double"; +#else + return "float"; +#endif + } +} + void BindingsGenerator::_populate_object_type_interfaces() { obj_types.clear(); @@ -2135,15 +2175,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } if (!ClassDB::is_class_exposed(type_cname)) { - if (verbose_output) - WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not exposed"); + _log("Ignoring type `%s` because it's not exposed\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } if (!ClassDB::is_class_enabled(type_cname)) { - if (verbose_output) - WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not enabled"); + _log("Ignoring type `%s` because it's not enabled\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } @@ -2154,7 +2192,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { itype.base_name = ClassDB::get_parent_class(type_cname); itype.is_singleton = Engine::get_singleton()->has_singleton(itype.proxy_name); - itype.is_instantiable = ClassDB::can_instance(type_cname) && !itype.is_singleton; + itype.is_instantiable = class_info->creation_func && !itype.is_singleton; itype.is_reference = ClassDB::is_parent_class(type_cname, name_cache.type_Reference); itype.memory_own = itype.is_reference; @@ -2171,10 +2209,12 @@ void BindingsGenerator::_populate_object_type_interfaces() { itype.im_type_in = "IntPtr"; itype.im_type_out = itype.proxy_name; + // Populate properties + List<PropertyInfo> property_list; ClassDB::get_property_list(type_cname, &property_list, true); - // Populate properties + Map<StringName, StringName> accessor_methods; for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { const PropertyInfo &property = E->get(); @@ -2187,18 +2227,21 @@ void BindingsGenerator::_populate_object_type_interfaces() { iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname); iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname); + if (iprop.setter != StringName()) + accessor_methods[iprop.setter] = iprop.cname; + if (iprop.getter != StringName()) + accessor_methods[iprop.getter] = iprop.cname; + bool valid = false; iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid); ERR_FAIL_COND(!valid); iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname)); - // Prevent property and enclosing type from sharing the same name + // Prevent the property and its enclosing type from sharing the same name if (iprop.proxy_name == itype.proxy_name) { - if (verbose_output) { - WARN_PRINTS("Name of property `" + iprop.proxy_name + "` is ambiguous with the name of its class `" + - itype.proxy_name + "`. Renaming property to `" + iprop.proxy_name + "_`"); - } + _log("Name of property `%s` is ambiguous with the name of its enclosing class `%s`. Renaming property to `%s_`\n", + iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data()); iprop.proxy_name += "_"; } @@ -2236,9 +2279,14 @@ void BindingsGenerator::_populate_object_type_interfaces() { if (method_info.name.empty()) continue; + String cname = method_info.name; + + if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname)) + continue; + MethodInterface imethod; imethod.name = method_info.name; - imethod.cname = imethod.name; + imethod.cname = cname; if (method_info.flags & METHOD_FLAG_VIRTUAL) imethod.is_virtual = true; @@ -2265,20 +2313,26 @@ void BindingsGenerator::_populate_object_type_interfaces() { // which could actually will return something different. // Let's put this to notify us if that ever happens. if (itype.cname != name_cache.type_Object || imethod.name != "free") { - if (verbose_output) { - WARN_PRINTS("Notification: New unexpected virtual non-overridable method found.\n" - "We only expected Object.free, but found " + - itype.name + "." + imethod.name); - } + ERR_PRINTS("Notification: New unexpected virtual non-overridable method found.\n" + "We only expected Object.free, but found " + + itype.name + "." + imethod.name); } } else { - ERR_PRINTS("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); + ERR_EXPLAIN("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); + ERR_FAIL(); } } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { imethod.return_type.cname = return_info.class_name; imethod.return_type.is_enum = true; } else if (return_info.class_name != StringName()) { imethod.return_type.cname = return_info.class_name; + if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) { + /* clang-format off */ + ERR_PRINTS("Return type is reference but hint is not " _STR(PROPERTY_HINT_RESOURCE_TYPE) "." + " Are you returning a reference type by pointer? Method: " + itype.name + "." + imethod.name); + /* clang-format on */ + ERR_FAIL(); + } } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -2286,7 +2340,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } else if (return_info.type == Variant::NIL) { imethod.return_type.cname = name_cache.type_void; } else { - imethod.return_type.cname = Variant::get_type_name(return_info.type); + if (return_info.type == Variant::INT) { + imethod.return_type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE); + } else if (return_info.type == Variant::REAL) { + imethod.return_type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE); + } else { + imethod.return_type.cname = Variant::get_type_name(return_info.type); + } } for (int i = 0; i < argc; i++) { @@ -2305,7 +2365,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::NIL) { iarg.type.cname = name_cache.type_Variant; } else { - iarg.type.cname = Variant::get_type_name(arginfo.type); + if (arginfo.type == Variant::INT) { + iarg.type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE); + } else if (arginfo.type == Variant::REAL) { + iarg.type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE); + } else { + iarg.type.cname = Variant::get_type_name(arginfo.type); + } } iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name)); @@ -2326,16 +2392,24 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name)); - // Prevent naming the property and its enclosing type from sharing the same name + // Prevent the method and its enclosing type from sharing the same name if (imethod.proxy_name == itype.proxy_name) { - if (verbose_output) { - WARN_PRINTS("Name of method `" + imethod.proxy_name + "` is ambiguous with the name of its class `" + - itype.proxy_name + "`. Renaming method to `" + imethod.proxy_name + "_`"); - } + _log("Name of method `%s` is ambiguous with the name of its enclosing class `%s`. Renaming method to `%s_`\n", + imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data()); imethod.proxy_name += "_"; } + Map<StringName, StringName>::Element *accessor = accessor_methods.find(imethod.cname); + if (accessor) { + const PropertyInterface *accessor_property = itype.find_property_by_name(accessor->value()); + + // We only deprecate an accessor method if it's in the same class as the property. It's easier this way, but also + // we don't know if an accessor method in a different class could have other purposes, so better leave those untouched. + imethod.is_deprecated = true; + imethod.deprecation_message = imethod.proxy_name + " is deprecated. Use the " + accessor_property->proxy_name + " property instead."; + } + if (itype.class_doc) { for (int i = 0; i < itype.class_doc->methods.size(); i++) { if (itype.class_doc->methods[i].name == imethod.name) { @@ -2362,8 +2436,8 @@ void BindingsGenerator::_populate_object_type_interfaces() { // Populate enums and constants - List<String> constant_list; - ClassDB::get_integer_constant_list(type_cname, &constant_list, true); + List<String> constants; + ClassDB::get_integer_constant_list(type_cname, &constants, true); const HashMap<StringName, List<StringName> > &enum_map = class_info->enum_map; const StringName *k = NULL; @@ -2378,13 +2452,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { enum_proxy_cname = StringName(enum_proxy_name); } EnumInterface ienum(enum_proxy_cname); - const List<StringName> &constants = enum_map.get(*k); - for (const List<StringName>::Element *E = constants.front(); E; E = E->next()) { + const List<StringName> &enum_constants = enum_map.get(*k); + for (const List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { const StringName &constant_cname = E->get(); String constant_name = constant_cname.operator String(); int *value = class_info->constant_map.getptr(constant_cname); ERR_FAIL_NULL(value); - constant_list.erase(constant_name); + constants.erase(constant_name); ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); @@ -2416,7 +2490,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { enum_types.insert(enum_itype.cname, enum_itype); } - for (const List<String>::Element *E = constant_list.front(); E; E = E->next()) { + for (const List<String>::Element *E = constants.front(); E; E = E->next()) { const String &constant_name = E->get(); int *value = class_info->constant_map.getptr(StringName(E->get())); ERR_FAIL_NULL(value); @@ -2448,13 +2522,8 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg switch (p_val.get_type()) { case Variant::NIL: - if (ClassDB::class_exists(r_iarg.type.cname)) { - // Object type - r_iarg.default_argument = "null"; - } else { - // Variant - r_iarg.default_argument = "null"; - } + // Either Object type or Variant + r_iarg.default_argument = "null"; break; // Atomic types case Variant::BOOL: @@ -2563,7 +2632,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { // bool itype = TypeInterface::create_value_type(String("bool")); - { // MonoBoolean <---> bool itype.c_in = "\t%0 %1_in = (%0)%1;\n"; @@ -2577,45 +2645,73 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = itype.name; builtin_types.insert(itype.cname, itype); - // int - // C interface is the same as that of enums. Remember to apply any - // changes done here to TypeInterface::postsetup_enum_type as well - itype = TypeInterface::create_value_type(String("int")); - itype.c_arg_in = "&%s_in"; + // Integer types { - // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_type = "int64_t"; + // C interface for 'uint32_t' is the same as that of enums. Remember to apply + // any of the changes done here to 'TypeInterface::postsetup_enum_type' as well. +#define INSERT_INT_TYPE(m_name, m_c_type_in_out, m_c_type) \ + { \ + itype = TypeInterface::create_value_type(String(m_name)); \ + { \ + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; \ + itype.c_out = "\treturn (%0)%1;\n"; \ + itype.c_type = #m_c_type; \ + itype.c_arg_in = "&%s_in"; \ + } \ + itype.c_type_in = #m_c_type_in_out; \ + itype.c_type_out = itype.c_type_in; \ + itype.im_type_in = itype.name; \ + itype.im_type_out = itype.name; \ + builtin_types.insert(itype.cname, itype); \ } - itype.c_type_in = "int32_t"; - itype.c_type_out = itype.c_type_in; - itype.im_type_in = itype.name; - itype.im_type_out = itype.name; - builtin_types.insert(itype.cname, itype); - // real_t - itype = TypeInterface(); - itype.name = "float"; // The name is always "float" in Variant, even with REAL_T_IS_DOUBLE. - itype.cname = itype.name; -#ifdef REAL_T_IS_DOUBLE - itype.proxy_name = "double"; -#else - itype.proxy_name = "float"; -#endif + // The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type' + + INSERT_INT_TYPE("sbyte", int8_t, int64_t); + INSERT_INT_TYPE("short", int16_t, int64_t); + INSERT_INT_TYPE("int", int32_t, int64_t); + INSERT_INT_TYPE("long", int64_t, int64_t); + INSERT_INT_TYPE("byte", uint8_t, int64_t); + INSERT_INT_TYPE("ushort", uint16_t, int64_t); + INSERT_INT_TYPE("uint", uint32_t, int64_t); + INSERT_INT_TYPE("ulong", uint64_t, int64_t); + } + + // Floating point types { - // The expected type for parameters and return value in ptrcall is 'double'. - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; + // float + itype = TypeInterface(); + itype.name = "float"; + itype.cname = itype.name; + itype.proxy_name = "float"; + { + // The expected type for 'float' in ptrcall is 'double' + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_type = "double"; + itype.c_type_in = "float"; + itype.c_type_out = "float"; + itype.c_arg_in = "&%s_in"; + } + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.cname, itype); + + // double + itype = TypeInterface(); + itype.name = "double"; + itype.cname = itype.name; + itype.proxy_name = "double"; itype.c_type = "double"; - itype.c_type_in = "real_t"; - itype.c_type_out = "real_t"; - itype.c_arg_in = "&%s_in"; + itype.c_type_in = "double"; + itype.c_type_out = "double"; + itype.c_arg_in = "&%s"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.cname, itype); } - itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; - builtin_types.insert(itype.cname, itype); // String itype = TypeInterface(); @@ -2865,12 +2961,32 @@ void BindingsGenerator::_populate_global_constants() { } } -void BindingsGenerator::initialize() { +void BindingsGenerator::_initialize_blacklisted_methods() { + + blacklisted_methods["Object"].push_back("to_string"); // there is already ToString + blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead + blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it) +} + +void BindingsGenerator::_log(const char *p_format, ...) { + + if (log_print_enabled) { + va_list list; + + va_start(list, p_format); + OS::get_singleton()->print("%s", str_format(p_format, list).utf8().get_data()); + va_end(list); + } +} + +void BindingsGenerator::_initialize() { EditorHelp::generate_doc(); enum_types.clear(); + _initialize_blacklisted_methods(); + _populate_object_type_interfaces(); _populate_builtin_type_interfaces(); @@ -2888,41 +3004,49 @@ void BindingsGenerator::initialize() { void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { const int NUM_OPTIONS = 2; - int options_left = NUM_OPTIONS; + String generate_all_glue_option = "--generate-mono-glue"; + String generate_cs_glue_option = "--generate-mono-cs-glue"; + String generate_cpp_glue_option = "--generate-mono-cpp-glue"; - String mono_glue_option = "--generate-mono-glue"; - String cs_api_option = "--generate-cs-api"; + String glue_dir_path; + String cs_dir_path; + String cpp_dir_path; - verbose_output = true; + int options_left = NUM_OPTIONS; const List<String>::Element *elem = p_cmdline_args.front(); while (elem && options_left) { - - if (elem->get() == mono_glue_option) { - + if (elem->get() == generate_all_glue_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - if (get_singleton()->generate_glue(path_elem->get()) != OK) - ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue"); + glue_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(mono_glue_option + ": No output directory specified"); + ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)"); } --options_left; + } else if (elem->get() == generate_cs_glue_option) { + const List<String>::Element *path_elem = elem->next(); - } else if (elem->get() == cs_api_option) { + if (path_elem) { + cs_dir_path = path_elem->get(); + elem = elem->next(); + } else { + ERR_PRINTS(generate_cs_glue_option + ": No output directory specified"); + } + --options_left; + } else if (elem->get() == generate_cpp_glue_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - if (get_singleton()->generate_cs_api(path_elem->get()) != OK) - ERR_PRINTS(cs_api_option + ": Failed to generate the C# API"); + cpp_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(cs_api_option + ": No output directory specified"); + ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified"); } --options_left; @@ -2931,10 +3055,31 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) elem = elem->next(); } - verbose_output = false; + if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) { + BindingsGenerator bindings_generator; + bindings_generator.set_log_print_enabled(true); - if (options_left != NUM_OPTIONS) + if (glue_dir_path.length()) { + if (bindings_generator.generate_glue(glue_dir_path) != OK) + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue"); + + if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK) + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API"); + } + + if (cs_dir_path.length()) { + if (bindings_generator.generate_cs_api(cs_dir_path) != OK) + ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API"); + } + + if (cpp_dir_path.length()) { + if (bindings_generator.generate_glue(cpp_dir_path) != OK) + ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue"); + } + + // Exit once done ::exit(0); + } } #endif diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 42071f9c0d..8be51a6c55 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -32,7 +32,7 @@ #define BINDINGS_GENERATOR_H #include "core/class_db.h" -#include "dotnet_solution.h" +#include "core/string_builder.h" #include "editor/doc/doc_data.h" #include "editor/editor_help.h" @@ -158,17 +158,20 @@ class BindingsGenerator { const DocData::MethodDoc *method_doc; + bool is_deprecated; + String deprecation_message; + void add_argument(const ArgumentInterface &argument) { arguments.push_back(argument); } MethodInterface() { - return_type.cname = BindingsGenerator::get_singleton()->name_cache.type_void; is_vararg = false; is_virtual = false; requires_object_call = false; is_internal = false; method_doc = NULL; + is_deprecated = false; } }; @@ -399,8 +402,8 @@ class BindingsGenerator { } static void postsetup_enum_type(TypeInterface &r_enum_itype) { - // C interface is the same as that of 'int'. Remember to apply any - // changes done here to the 'int' type interface as well + // C interface for enums is the same as that of 'uint32_t'. Remember to apply + // any of the changes done here to the 'uint32_t' type interface as well. r_enum_itype.c_arg_in = "&%s_in"; { @@ -468,7 +471,7 @@ class BindingsGenerator { } }; - static bool verbose_output; + bool log_print_enabled; OrderedHashMap<StringName, TypeInterface> obj_types; @@ -487,9 +490,12 @@ class BindingsGenerator { List<InternalCall> core_custom_icalls; List<InternalCall> editor_custom_icalls; + Map<StringName, List<StringName> > blacklisted_methods; + + void _initialize_blacklisted_methods(); + struct NameCache { StringName type_void; - StringName type_int; StringName type_Array; StringName type_Dictionary; StringName type_Variant; @@ -500,9 +506,19 @@ class BindingsGenerator { StringName type_at_GlobalScope; StringName enum_Error; + StringName type_sbyte; + StringName type_short; + StringName type_int; + StringName type_long; + StringName type_byte; + StringName type_ushort; + StringName type_uint; + StringName type_ulong; + StringName type_float; + StringName type_double; + NameCache() { type_void = StaticCString::create("void"); - type_int = StaticCString::create("int"); type_Array = StaticCString::create("Array"); type_Dictionary = StaticCString::create("Dictionary"); type_Variant = StaticCString::create("Variant"); @@ -512,8 +528,20 @@ class BindingsGenerator { type_String = StaticCString::create("String"); type_at_GlobalScope = StaticCString::create("@GlobalScope"); enum_Error = StaticCString::create("Error"); + + type_sbyte = StaticCString::create("sbyte"); + type_short = StaticCString::create("short"); + type_int = StaticCString::create("int"); + type_long = StaticCString::create("long"); + type_byte = StaticCString::create("byte"); + type_ushort = StaticCString::create("ushort"); + type_uint = StaticCString::create("uint"); + type_ulong = StaticCString::create("ulong"); + type_float = StaticCString::create("float"); + type_double = StaticCString::create("double"); } + private: NameCache(const NameCache &); NameCache &operator=(const NameCache &); }; @@ -559,6 +587,9 @@ class BindingsGenerator { const TypeInterface *_get_type_or_null(const TypeReference &p_typeref); const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref); + StringName _get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta); + StringName _get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta); + void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg); void _populate_object_type_interfaces(); @@ -568,42 +599,36 @@ class BindingsGenerator { Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file); - Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, List<String> &p_output); - Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, List<String> &p_output); + Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output); + Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output); - void _generate_global_constants(List<String> &p_output); + void _generate_global_constants(StringBuilder &p_output); - Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, List<String> &p_output); + Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output); - Error _save_file(const String &p_path, const List<String> &p_content); + Error _save_file(const String &p_path, const StringBuilder &p_content); - BindingsGenerator() {} + void _log(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; - BindingsGenerator(const BindingsGenerator &); - BindingsGenerator &operator=(const BindingsGenerator &); - - friend class CSharpLanguage; - static BindingsGenerator *singleton; + void _initialize(); public: - Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); - Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); - Error generate_cs_api(const String &p_output_dir, bool p_verbose_output = true); + Error generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_files); + Error generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items); + Error generate_cs_api(const String &p_output_dir); Error generate_glue(const String &p_output_dir); + _FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; } + _FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } + static uint32_t get_version(); - void initialize(); + static void handle_cmdline_args(const List<String> &p_cmdline_args); - _FORCE_INLINE_ static BindingsGenerator *get_singleton() { - if (!singleton) { - singleton = memnew(BindingsGenerator); - singleton->initialize(); - } - return singleton; + BindingsGenerator() : + log_print_enabled(true) { + _initialize(); } - - static void handle_cmdline_args(const List<String> &p_cmdline_args); }; #endif diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index beeff51bc2..d88b08c646 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -44,66 +44,54 @@ namespace CSharpProject { -String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); - - Variant dir = p_dir; - Variant compile_items = p_files; - const Variant *args[2] = { &dir, &compile_items }; +bool generate_api_solution_impl(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items, + GDMonoAssembly *p_tools_project_editor_assembly) { + + GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator"); + + Variant solution_dir = p_solution_dir; + Variant core_proj_dir = p_core_proj_dir; + Variant core_compile_items = p_core_compile_items; + Variant editor_proj_dir = p_editor_proj_dir; + Variant editor_compile_items = p_editor_compile_items; + const Variant *args[5] = { &solution_dir, &core_proj_dir, &core_compile_items, &editor_proj_dir, &editor_compile_items }; MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &exc); + klass->get_method("GenerateApiSolution", 5)->invoke(NULL, args, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); + ERR_FAIL_V(false); } - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); + return true; } -String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); - - Variant dir = p_dir; - Variant core_proj_path = p_core_proj_path; - Variant compile_items = p_files; - const Variant *args[3] = { &dir, &core_proj_path, &compile_items }; - MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); - } - - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); -} +bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items) { -String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) { + if (GDMono::get_singleton()->get_tools_project_editor_assembly()) { + return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, + p_editor_proj_dir, p_editor_compile_items, + GDMono::get_singleton()->get_tools_project_editor_assembly()); + } else { + MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain"); + CRASH_COND(temp_domain == NULL); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain); - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + _GDMONO_SCOPE_DOMAIN_(temp_domain); - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); + GDMonoAssembly *tools_project_editor_assembly = NULL; - Variant dir = p_dir; - Variant name = p_name; - Variant compile_items = p_files; - const Variant *args[3] = { &dir, &name, &compile_items }; - MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &exc); + if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) { + ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'"); + ERR_FAIL_V(false); + } - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); + return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, + p_editor_proj_dir, p_editor_compile_items, + tools_project_editor_assembly); } - - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); } void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) { @@ -111,9 +99,9 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str if (!GLOBAL_DEF("mono/project/auto_update_project", true)) return; - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly(); - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); + GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils"); Variant project_path = p_project_path; Variant item_type = p_item_type; @@ -128,126 +116,4 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str } } -Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - if (FileAccess::exists(p_output_path)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error rm_err = da->remove(p_output_path); - - ERR_EXPLAIN("Failed to remove old scripts metadata file"); - ERR_FAIL_COND_V(rm_err != OK, rm_err); - } - - GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); - - void *args[2] = { - GDMonoMarshal::mono_string_from_godot(p_project_path), - GDMonoMarshal::mono_string_from_godot("Compile") - }; - - MonoException *exc = NULL; - MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(FAILED); - } - - PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret); - PoolStringArray::Read r = project_files.read(); - - Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata(); - Dictionary new_dict; - - for (int i = 0; i < project_files.size(); i++) { - const String &project_file = ("res://" + r[i]).simplify_path(); - - uint64_t modified_time = FileAccess::get_modified_time(project_file); - - const Variant *old_file_var = old_dict.getptr(project_file); - if (old_file_var) { - Dictionary old_file_dict = old_file_var->operator Dictionary(); - - if (old_file_dict["modified_time"].operator uint64_t() == modified_time) { - // No changes so no need to parse again - new_dict[project_file] = old_file_dict; - continue; - } - } - - ScriptClassParser scp; - Error err = scp.parse_file(project_file); - if (err != OK) { - ERR_PRINTS("Parse error: " + scp.get_error()); - ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file); - ERR_FAIL_V(err); - } - - Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes(); - - bool found = false; - Dictionary class_dict; - - String search_name = project_file.get_file().get_basename(); - - for (int j = 0; j < classes.size(); j++) { - const ScriptClassParser::ClassDecl &class_decl = classes[j]; - - if (class_decl.base.size() == 0) - continue; // Does not inherit nor implement anything, so it can't be a script class - - String class_cmp; - - if (class_decl.nested) { - class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1); - } else { - class_cmp = class_decl.name; - } - - if (class_cmp != search_name) - continue; - - class_dict["namespace"] = class_decl.namespace_; - class_dict["class_name"] = class_decl.name; - class_dict["nested"] = class_decl.nested; - - found = true; - break; - } - - if (found) { - Dictionary file_dict; - file_dict["modified_time"] = modified_time; - file_dict["class"] = class_dict; - new_dict[project_file] = file_dict; - } - } - - if (new_dict.size()) { - String json = JSON::print(new_dict, "", false); - - String base_dir = p_output_path.get_base_dir(); - - if (!DirAccess::exists(base_dir)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - - Error err = da->make_dir_recursive(base_dir); - ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); - } - - Error ferr; - FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr); - ERR_EXPLAIN("Cannot open file for writing: " + p_output_path); - ERR_FAIL_COND_V(ferr != OK, ferr); - f->store_string(json); - f->flush(); - f->close(); - memdelete(f); - } - - return OK; -} - } // namespace CSharpProject diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h index 3d5a65f8da..b42762cea2 100644 --- a/modules/mono/editor/csharp_project.h +++ b/modules/mono/editor/csharp_project.h @@ -35,14 +35,11 @@ namespace CSharpProject { -String generate_core_api_project(const String &p_dir, const Vector<String> &p_files = Vector<String>()); -String generate_editor_api_project(const String &p_dir, const String &p_core_dll_path, const Vector<String> &p_files = Vector<String>()); -String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>()); +bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items); void add_item(const String &p_project_path, const String &p_item_type, const String &p_include); -Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path); - } // namespace CSharpProject #endif // CSHARP_PROJECT_H diff --git a/modules/mono/editor/dotnet_solution.cpp b/modules/mono/editor/dotnet_solution.cpp deleted file mode 100644 index 324752cafc..0000000000 --- a/modules/mono/editor/dotnet_solution.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/*************************************************************************/ -/* dotnet_solution.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "dotnet_solution.h" - -#include "core/os/dir_access.h" -#include "core/os/file_access.h" - -#include "../utils/path_utils.h" -#include "../utils/string_utils.h" -#include "csharp_project.h" - -#define SOLUTION_TEMPLATE \ - "Microsoft Visual Studio Solution File, Format Version 12.00\n" \ - "# Visual Studio 2012\n" \ - "%0\n" \ - "Global\n" \ - "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n" \ - "%1\n" \ - "\tEndGlobalSection\n" \ - "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n" \ - "%2\n" \ - "\tEndGlobalSection\n" \ - "EndGlobal\n" - -#define PROJECT_DECLARATION "Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"%0\", \"%1\", \"{%2}\"\nEndProject" - -#define SOLUTION_PLATFORMS_CONFIG "\t%0|Any CPU = %0|Any CPU" - -#define PROJECT_PLATFORMS_CONFIG \ - "\t\t{%0}.%1|Any CPU.ActiveCfg = %1|Any CPU\n" \ - "\t\t{%0}.%1|Any CPU.Build.0 = %1|Any CPU" - -void DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) { - projects[p_name] = p_project_info; -} - -bool DotNetSolution::has_project(const String &p_name) const { - return projects.find(p_name) != NULL; -} - -const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const { - return projects[p_name]; -} - -bool DotNetSolution::remove_project(const String &p_name) { - return projects.erase(p_name); -} - -Error DotNetSolution::save() { - bool dir_exists = DirAccess::exists(path); - ERR_EXPLAIN("The directory does not exist."); - ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND); - - String projs_decl; - String sln_platform_cfg; - String proj_platform_cfg; - - for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) { - const String &name = E->key(); - const ProjectInfo &proj_info = E->value(); - - bool is_front = E == projects.front(); - - if (!is_front) - projs_decl += "\n"; - - projs_decl += sformat(PROJECT_DECLARATION, name, proj_info.relpath.replace("/", "\\"), proj_info.guid); - - for (int i = 0; i < proj_info.configs.size(); i++) { - const String &config = proj_info.configs[i]; - - if (i != 0 || !is_front) { - sln_platform_cfg += "\n"; - proj_platform_cfg += "\n"; - } - - sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config); - proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, proj_info.guid, config); - } - } - - String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg); - - FileAccess *file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE); - ERR_FAIL_NULL_V(file, ERR_FILE_CANT_WRITE); - file->store_string(content); - file->close(); - memdelete(file); - - return OK; -} - -bool DotNetSolution::set_path(const String &p_existing_path) { - if (p_existing_path.is_abs_path()) { - path = p_existing_path; - } else { - String abspath; - if (!rel_path_to_abs(p_existing_path, abspath)) - return false; - path = abspath; - } - - return true; -} - -String DotNetSolution::get_path() { - return path; -} - -DotNetSolution::DotNetSolution(const String &p_name) { - name = p_name; -} diff --git a/modules/mono/editor/dotnet_solution.h b/modules/mono/editor/dotnet_solution.h deleted file mode 100644 index 18933364fa..0000000000 --- a/modules/mono/editor/dotnet_solution.h +++ /dev/null @@ -1,63 +0,0 @@ -/*************************************************************************/ -/* dotnet_solution.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef NET_SOLUTION_H -#define NET_SOLUTION_H - -#include "core/map.h" -#include "core/ustring.h" - -struct DotNetSolution { - String name; - - struct ProjectInfo { - String guid; - String relpath; // Must be relative to the solution directory - Vector<String> configs; - }; - - void add_new_project(const String &p_name, const ProjectInfo &p_project_info); - bool has_project(const String &p_name) const; - const ProjectInfo &get_project_info(const String &p_name) const; - bool remove_project(const String &p_name); - - Error save(); - - bool set_path(const String &p_existing_path); - String get_path(); - - DotNetSolution(const String &p_name); - -private: - String path; - Map<String, ProjectInfo> projects; -}; - -#endif // NET_SOLUTION_H diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp new file mode 100644 index 0000000000..a3b5b450ef --- /dev/null +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -0,0 +1,429 @@ +/*************************************************************************/ +/* editor_internal_calls.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_internal_calls.h" + +#include "core/message_queue.h" +#include "core/os/os.h" +#include "core/version.h" +#include "editor/editor_node.h" +#include "editor/plugins/script_editor_plugin.h" +#include "editor/script_editor_debugger.h" +#include "main/main.h" + +#include "../csharp_script.h" +#include "../glue/cs_glue_version.gen.h" +#include "../godotsharp_dirs.h" +#include "../mono_gd/gd_mono_marshal.h" +#include "../utils/osx_utils.h" +#include "bindings_generator.h" +#include "godotsharp_export.h" +#include "script_class_parser.h" + +MonoString *godot_icall_GodotSharpDirs_ResDataDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResConfigDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoUserDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() { +#ifdef WINDOWS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir()); +#else + return NULL; +#endif +} + +void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + String label = GDMonoMarshal::mono_string_to_godot(p_label); + EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel); +} + +void godot_icall_EditorProgress_Dispose(MonoString *p_task) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + EditorNode::progress_end_task(task); +} + +MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + String state = GDMonoMarshal::mono_string_to_godot(p_state); + return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); +} + +BindingsGenerator *godot_icall_BindingsGenerator_Ctor() { + return memnew(BindingsGenerator); +} + +void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) { + memdelete(p_handle); +} + +MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) { + return p_handle->is_log_print_enabled(); +} + +void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) { + p_handle.set_log_print_enabled(p_enabled); +} + +int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) { + String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir); + return p_handle->generate_cs_api(output_dir); +} + +uint32_t godot_icall_BindingsGenerator_Version() { + return BindingsGenerator::get_version(); +} + +uint32_t godot_icall_BindingsGenerator_CsGlueVersion() { + return CS_GLUE_VERSION; +} + +int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes) { + String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); + + ScriptClassParser scp; + Error err = scp.parse_file(filepath); + if (err == OK) { + Array classes = GDMonoMarshal::mono_object_to_variant(p_classes); + const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes(); + + for (int i = 0; i < class_decls.size(); i++) { + const ScriptClassParser::ClassDecl &classDecl = class_decls[i]; + + Dictionary classDeclDict; + classDeclDict["name"] = classDecl.name; + classDeclDict["namespace"] = classDecl.namespace_; + classDeclDict["nested"] = classDecl.nested; + classDeclDict["base_count"] = classDecl.base.size(); + classes.push_back(classDeclDict); + } + } + return err; +} + +uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path, + MonoString *p_build_config, MonoString *p_custom_lib_dir, MonoObject *r_dependencies) { + String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name); + String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path); + String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config); + String custom_lib_dir = GDMonoMarshal::mono_string_to_godot(p_custom_lib_dir); + Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies); + + return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies); +} + +float godot_icall_Internal_EditorScale() { + return EDSCALE; +} + +MonoObject *godot_icall_Internal_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoObject *godot_icall_Internal_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoString *godot_icall_Internal_FullTemplatesDir() { + String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + return GDMonoMarshal::mono_string_from_godot(full_templates_dir); +} + +MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { + String path = GDMonoMarshal::mono_string_to_godot(p_path); + return GDMonoMarshal::mono_string_from_godot(path.simplify_path()); +} + +MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) { +#ifdef OSX_ENABLED + String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); + return (MonoBoolean)osx_is_app_bundle_installed; +#else + (void)p_bundle_id; // UNUSED + return (MonoBoolean) false; +#endif +} + +MonoBoolean godot_icall_Internal_MetadataIsApiAssemblyInvalidated(int32_t p_api_type) { + return GDMono::get_singleton()->metadata_is_api_assembly_invalidated((APIAssembly::Type)p_api_type); +} + +void godot_icall_Internal_MetadataSetApiAssemblyInvalidated(int32_t p_api_type, MonoBoolean p_invalidated) { + GDMono::get_singleton()->metadata_set_api_assembly_invalidated((APIAssembly::Type)p_api_type, (bool)p_invalidated); +} + +MonoBoolean godot_icall_Internal_IsMessageQueueFlushing() { + return (MonoBoolean)MessageQueue::get_singleton()->is_flushing(); +} + +MonoBoolean godot_icall_Internal_GodotIs32Bits() { + return sizeof(void *) == 4; +} + +MonoBoolean godot_icall_Internal_GodotIsRealTDouble() { +#ifdef REAL_T_IS_DOUBLE + return (MonoBoolean) true; +#else + return (MonoBoolean) false; +#endif +} + +void godot_icall_Internal_GodotMainIteration() { + Main::iteration(); +} + +uint64_t godot_icall_Internal_GetCoreApiHash() { + return ClassDB::get_api_hash(ClassDB::API_CORE); +} + +uint64_t godot_icall_Internal_GetEditorApiHash() { + return ClassDB::get_api_hash(ClassDB::API_EDITOR); +} + +MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() { +#ifdef GD_MONO_HOT_RELOAD + return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed(); +#else + return (MonoBoolean) false; +#endif +} + +void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) { +#ifdef GD_MONO_HOT_RELOAD + _GodotSharp::get_singleton()->call_deferred("_reload_assemblies", (bool)p_soft_reload); +#endif +} + +void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() { + ScriptEditor::get_singleton()->get_debugger()->reload_scripts(); +} + +MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) { + Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource); + return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus); +} + +void godot_icall_Internal_EditorNodeShowScriptScreen() { + EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); +} + +MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) { + Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); + + MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype); + + uint32_t type_encoding = mono_type_get_type(dict_type); + MonoClass *type_class_raw = mono_class_from_mono_type(dict_type); + GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw); + + return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class)); +} + +MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { +#ifdef WINDOWS_ENABLED + String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; + return GDMonoMarshal::mono_string_from_godot(install_root_dir); +#else + return NULL; +#endif +} + +MonoString *godot_icall_Utils_OS_GetPlatformName() { + String os_name = OS::get_singleton()->get_name(); + return GDMonoMarshal::mono_string_from_godot(os_name); +} + +void register_editor_internal_calls() { + + // GodotSharpDirs + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir); + + // EditorProgress + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create); + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose); + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step); + + // BiningsGenerator + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion); + + // ScriptClassParser + mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile); + + // GodotSharpExport + mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies); + + // Internals + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorScale", (void *)godot_icall_Internal_EditorScale); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GlobalDef", (void *)godot_icall_Internal_GlobalDef); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorDef", (void *)godot_icall_Internal_EditorDef); + mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir); + mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataIsApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataIsApiAssemblyInvalidated); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataSetApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataSetApiAssemblyInvalidated); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsMessageQueueFlushing", (void *)godot_icall_Internal_IsMessageQueueFlushing); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); + + // Utils.OS + mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); +} diff --git a/modules/mono/editor/monodevelop_instance.h b/modules/mono/editor/editor_internal_calls.h index 3b3af9607b..1682da66e5 100644 --- a/modules/mono/editor/monodevelop_instance.h +++ b/modules/mono/editor/editor_internal_calls.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* monodevelop_instance.h */ +/* editor_internal_calls.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef MONODEVELOP_INSTANCE_H -#define MONODEVELOP_INSTANCE_H +#ifndef EDITOR_INTERNAL_CALL_H +#define EDITOR_INTERNAL_CALL_H -#include "core/reference.h" +void register_editor_internal_calls(); -#include "../mono_gc_handle.h" -#include "../mono_gd/gd_mono_method.h" - -class MonoDevelopInstance { - - Ref<MonoGCHandle> gc_handle; - GDMonoMethod *execute_method; - -public: - enum EditorId { - MONODEVELOP = 0, - VISUALSTUDIO_FOR_MAC = 1 - }; - - void execute(const Vector<String> &p_files); - void execute(const String &p_file); - - MonoDevelopInstance(const String &p_solution, EditorId p_editor_id); -}; - -#endif // MONODEVELOP_INSTANCE_H +#endif // EDITOR_INTERNAL_CALL_H diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp deleted file mode 100644 index 00c780d1b7..0000000000 --- a/modules/mono/editor/godotsharp_builds.cpp +++ /dev/null @@ -1,602 +0,0 @@ -/*************************************************************************/ -/* godotsharp_builds.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "godotsharp_builds.h" - -#include "core/vector.h" -#include "main/main.h" - -#include "../glue/cs_glue_version.gen.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../utils/path_utils.h" -#include "bindings_generator.h" -#include "csharp_project.h" -#include "godotsharp_editor.h" - -#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)" -#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)" -#define PROP_NAME_XBUILD "xbuild (Deprecated)" - -void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) { - - String solution = GDMonoMarshal::mono_string_to_godot(p_solution); - String config = GDMonoMarshal::mono_string_to_godot(p_config); - GodotSharpBuilds::get_singleton()->build_exit_callback(MonoBuildInfo(solution, config), p_exit_code); -} - -static Vector<const char *> _get_msbuild_hint_dirs() { - Vector<const char *> ret; -#ifdef OSX_ENABLED - ret.push_back("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); - ret.push_back("/usr/local/var/homebrew/linked/mono/bin/"); -#endif - ret.push_back("/opt/novell/mono/bin/"); - return ret; -} - -#ifdef UNIX_ENABLED -String _find_build_engine_on_unix(const String &p_name) { - String ret = path_which(p_name); - - if (ret.length()) - return ret; - - String ret_fallback = path_which(p_name + ".exe"); - if (ret_fallback.length()) - return ret_fallback; - - static Vector<const char *> locations = _get_msbuild_hint_dirs(); - - for (int i = 0; i < locations.size(); i++) { - String hint_path = locations[i] + p_name; - - if (FileAccess::exists(hint_path)) { - return hint_path; - } - } - - return String(); -} -#endif - -MonoString *godot_icall_BuildInstance_get_MSBuildPath() { - - GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))); - -#if defined(WINDOWS_ENABLED) - switch (build_tool) { - case GodotSharpBuilds::MSBUILD_VS: { - static String msbuild_tools_path; - - if (msbuild_tools_path.empty() || !FileAccess::exists(msbuild_tools_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path(); - - if (msbuild_tools_path.empty()) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Tried with path: " + msbuild_tools_path); - return NULL; - } - } - - if (!msbuild_tools_path.ends_with("\\")) - msbuild_tools_path += "\\"; - - return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe"); - } break; - case GodotSharpBuilds::MSBUILD_MONO: { - String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat"); - - if (!FileAccess::exists(msbuild_path)) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path); - return NULL; - } - - return GDMonoMarshal::mono_string_from_godot(msbuild_path); - } break; - case GodotSharpBuilds::XBUILD: { - String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat"); - - if (!FileAccess::exists(xbuild_path)) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path); - return NULL; - } - - return GDMonoMarshal::mono_string_from_godot(xbuild_path); - } break; - default: - ERR_EXPLAIN("You don't deserve to live"); - CRASH_NOW(); - } -#elif defined(UNIX_ENABLED) - static String msbuild_path; - static String xbuild_path; - - if (build_tool == GodotSharpBuilds::XBUILD) { - if (xbuild_path.empty() || !FileAccess::exists(xbuild_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - xbuild_path = _find_build_engine_on_unix("msbuild"); - } - - if (xbuild_path.empty()) { - ERR_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'"); - return NULL; - } - } else { - if (msbuild_path.empty() || !FileAccess::exists(msbuild_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - msbuild_path = _find_build_engine_on_unix("msbuild"); - } - - if (msbuild_path.empty()) { - ERR_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'"); - return NULL; - } - } - - return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path); -#else - (void)build_tool; // UNUSED - - ERR_EXPLAIN("Not implemented on this platform"); - ERR_FAIL_V(NULL); -#endif -} - -MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() { - -#if defined(WINDOWS_ENABLED) - const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info(); - if (mono_reg_info.bin_dir.length()) { - return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir); - } - - ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry"); - ERR_FAIL_V(NULL); -#else - return NULL; -#endif -} - -MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() { - -#if defined(WINDOWS_ENABLED) - return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO; -#else - return false; -#endif -} - -MonoBoolean godot_icall_BuildInstance_get_PrintBuildOutput() { - - return (bool)EDITOR_GET("mono/builds/print_build_output"); -} - -void GodotSharpBuilds::register_internal_calls() { - - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_PrintBuildOutput", (void *)godot_icall_BuildInstance_get_PrintBuildOutput); -} - -void GodotSharpBuilds::show_build_error_dialog(const String &p_message) { - - GodotSharpEditor::get_singleton()->show_error_dialog(p_message, "Build error"); - MonoBottomPanel::get_singleton()->show_build_tab(); -} - -bool GodotSharpBuilds::build_api_sln(const String &p_api_sln_dir, const String &p_config) { - - String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - - String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); - String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - - String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); - String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) { - MonoBuildInfo api_build_info(api_sln_file, p_config); - // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, - // once we start to actively document manually maintained C# classes - api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings - - if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) { - show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution."); - return false; - } - } - - return true; -} - -bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) { - - // Create destination directory if needed - if (!DirAccess::exists(p_dst_dir)) { - DirAccess *da = DirAccess::create_for_path(p_dst_dir); - Error err = da->make_dir_recursive(p_dst_dir); - memdelete(da); - - if (err != OK) { - show_build_error_dialog("Failed to create destination directory for the API assemblies. Error: " + itos(err)); - return false; - } - } - - String assembly_file = p_assembly_name + ".dll"; - String assembly_src = p_src_dir.plus_file(assembly_file); - String assembly_dst = p_dst_dir.plus_file(assembly_file); - - if (!FileAccess::exists(assembly_dst) || - FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - - String xml_file = p_assembly_name + ".xml"; - if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK) - WARN_PRINTS("Failed to copy " + xml_file); - - String pdb_file = p_assembly_name + ".pdb"; - if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK) - WARN_PRINTS("Failed to copy " + pdb_file); - - Error err = da->copy(assembly_src, assembly_dst); - - if (err != OK) { - show_build_error_dialog("Failed to copy " + assembly_file); - return false; - } - - GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false); - } - - return true; -} - -String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) { - - uint64_t api_hash = p_api_type == APIAssembly::API_CORE ? - GDMono::get_singleton()->get_api_core_hash() : - GDMono::get_singleton()->get_api_editor_hash(); - return String::num_uint64(api_hash) + - "_" + String::num_uint64(BindingsGenerator::get_version()) + - "_" + String::num_uint64(CS_GLUE_VERSION); -} - -bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) { - - String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; - - String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir(); - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - - if (FileAccess::exists(editor_prebuilt_api_dir.plus_file(api_name + ".dll"))) { - EditorProgress pr("mono_copy_prebuilt_api_assembly", "Copying prebuilt " + api_name + " assembly...", 1); - pr.step("Copying " + api_name + " assembly", 0); - return GodotSharpBuilds::copy_api_assembly(editor_prebuilt_api_dir, res_assemblies_dir, api_name, p_api_type); - } - - String api_build_config = "Release"; - - EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3); - - pr.step("Generating " API_SOLUTION_NAME " solution", 0); - - String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() - .plus_file(_api_folder_name(APIAssembly::API_CORE)); - - String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - - if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { - BindingsGenerator *gen = BindingsGenerator::get_singleton(); - bool gen_verbose = OS::get_singleton()->is_stdout_verbose(); - - Error err = gen->generate_cs_api(api_sln_dir, gen_verbose); - if (err != OK) { - show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err)); - return false; - } - } - - pr.step("Building " API_SOLUTION_NAME " solution", 1); - - if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config)) - return false; - - pr.step("Copying " + api_name + " assembly", 2); - - // Copy the built assembly to the assemblies directory - String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config); - if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type)) - return false; - - return true; -} - -bool GodotSharpBuilds::build_project_blocking(const String &p_config) { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return true; // No solution to build - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return false; - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return false; - - EditorProgress pr("mono_project_debug_build", "Building project solution...", 1); - pr.step("Building project solution", 0); - - MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config); - if (!GodotSharpBuilds::get_singleton()->build(build_info)) { - GodotSharpBuilds::show_build_error_dialog("Failed to build project solution"); - return false; - } - - return true; -} - -bool GodotSharpBuilds::editor_build_callback() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return true; // No solution to build - - String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); - String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player"); - - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor); - ERR_FAIL_COND_V(metadata_err != OK, false); - - if (FileAccess::exists(scripts_metadata_path_editor)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player); - - ERR_EXPLAIN("Failed to copy scripts metadata file"); - ERR_FAIL_COND_V(copy_err != OK, false); - } - - return build_project_blocking("Tools"); -} - -GodotSharpBuilds *GodotSharpBuilds::singleton = NULL; - -void GodotSharpBuilds::build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code) { - - BuildProcess *match = builds.getptr(p_build_info); - ERR_FAIL_NULL(match); - - BuildProcess &bp = *match; - bp.on_exit(p_exit_code); -} - -void GodotSharpBuilds::restart_build(MonoBuildTab *p_build_tab) { -} - -void GodotSharpBuilds::stop_build(MonoBuildTab *p_build_tab) { -} - -bool GodotSharpBuilds::build(const MonoBuildInfo &p_build_info) { - - BuildProcess *match = builds.getptr(p_build_info); - - if (match) { - BuildProcess &bp = *match; - bp.start(true); - return bp.exit_code == 0; - } else { - BuildProcess bp = BuildProcess(p_build_info); - bp.start(true); - builds.set(p_build_info, bp); - return bp.exit_code == 0; - } -} - -bool GodotSharpBuilds::build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) { - - BuildProcess *match = builds.getptr(p_build_info); - - if (match) { - BuildProcess &bp = *match; - bp.start(); - return !bp.exited; // failed to start - } else { - BuildProcess bp = BuildProcess(p_build_info, p_callback); - bp.start(); - builds.set(p_build_info, bp); - return !bp.exited; // failed to start - } -} - -GodotSharpBuilds::GodotSharpBuilds() { - - singleton = this; - - EditorNode::get_singleton()->add_build_callback(&GodotSharpBuilds::editor_build_callback); - - // Build tool settings - EditorSettings *ed_settings = EditorSettings::get_singleton(); - -#ifdef WINDOWS_ENABLED - EDITOR_DEF("mono/builds/build_tool", MSBUILD_VS); -#else - EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO); -#endif - - ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM, - PROP_NAME_MSBUILD_MONO -#ifdef WINDOWS_ENABLED - "," PROP_NAME_MSBUILD_VS -#endif - "," PROP_NAME_XBUILD)); - - EDITOR_DEF("mono/builds/print_build_output", false); -} - -GodotSharpBuilds::~GodotSharpBuilds() { - - singleton = NULL; -} - -void GodotSharpBuilds::BuildProcess::on_exit(int p_exit_code) { - - exited = true; - exit_code = p_exit_code; - build_tab->on_build_exit(p_exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); - build_instance.unref(); - - if (exit_callback) - exit_callback(exit_code); -} - -void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - exit_code = -1; - - String log_dirpath = build_info.get_log_dirpath(); - - if (build_tab) { - build_tab->on_build_start(); - } else { - build_tab = memnew(MonoBuildTab(build_info, log_dirpath)); - MonoBottomPanel::get_singleton()->add_build_tab(build_tab); - } - - if (p_blocking) { - // Required in order to update the build tasks list - Main::iteration(); - } - - if (!exited) { - exited = true; - String message = "Tried to start build process, but it is already running"; - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - exited = false; - - // Remove old issues file - - String issues_file = get_msbuild_issues_filename(); - DirAccessRef d = DirAccess::create_for_path(log_dirpath); - if (d->file_exists(issues_file)) { - Error err = d->remove(issues_file); - if (err != OK) { - exited = true; - String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file); - String message = "Cannot remove issues file: " + file_path; - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - } - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Build", "BuildInstance"); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), klass->get_mono_ptr()); - - // Construct - - Variant solution = build_info.solution; - Variant config = build_info.configuration; - - const Variant *ctor_args[2] = { &solution, &config }; - - MonoException *exc = NULL; - GDMonoMethod *ctor = klass->get_method(".ctor", 2); - ctor->invoke(mono_object, ctor_args, &exc); - - if (exc) { - exited = true; - GDMonoUtils::debug_unhandled_exception(exc); - String message = "The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc); - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - // Call Build - - String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path(); - Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path); - Variant logger_output_dir = log_dirpath; - Variant custom_props = build_info.custom_props; - - const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props }; - - exc = NULL; - GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3); - build_method->invoke(mono_object, args, &exc); - - if (exc) { - exited = true; - GDMonoUtils::debug_unhandled_exception(exc); - String message = "The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc); - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - // Build returned - - if (p_blocking) { - exited = true; - exit_code = klass->get_field("exitCode")->get_int_value(mono_object); - - if (exit_code != 0) { - String log_filepath = build_info.get_log_dirpath().plus_file(get_msbuild_log_filename()); - print_verbose("MSBuild exited with code: " + itos(exit_code) + ". Log file: " + log_filepath); - } - - build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); - } else { - build_instance = MonoGCHandle::create_strong(mono_object); - exited = false; - } -} - -GodotSharpBuilds::BuildProcess::BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) : - build_info(p_build_info), - build_tab(NULL), - exit_callback(p_callback), - exited(true), - exit_code(-1) { -} diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h deleted file mode 100644 index 652d30538a..0000000000 --- a/modules/mono/editor/godotsharp_builds.h +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************/ -/* godotsharp_builds.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GODOTSHARP_BUILDS_H -#define GODOTSHARP_BUILDS_H - -#include "../mono_gd/gd_mono.h" -#include "mono_bottom_panel.h" -#include "mono_build_info.h" - -typedef void (*GodotSharpBuild_ExitCallback)(int); - -class GodotSharpBuilds { - -private: - struct BuildProcess { - Ref<MonoGCHandle> build_instance; - MonoBuildInfo build_info; - MonoBuildTab *build_tab; - GodotSharpBuild_ExitCallback exit_callback; - bool exited; - int exit_code; - - void on_exit(int p_exit_code); - void start(bool p_blocking = false); - - BuildProcess() {} - BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); - }; - - HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds; - - static String _api_folder_name(APIAssembly::Type p_api_type); - - static GodotSharpBuilds *singleton; - -public: - enum BuildTool { - MSBUILD_MONO, -#ifdef WINDOWS_ENABLED - MSBUILD_VS, -#endif - XBUILD // Deprecated - }; - - _FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; } - - static void register_internal_calls(); - - static void show_build_error_dialog(const String &p_message); - - static const char *get_msbuild_issues_filename() { return "msbuild_issues.csv"; } - static const char *get_msbuild_log_filename() { return "msbuild_log.txt"; } - - void build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code); - - void restart_build(MonoBuildTab *p_build_tab); - void stop_build(MonoBuildTab *p_build_tab); - - bool build(const MonoBuildInfo &p_build_info); - bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); - - static bool build_api_sln(const String &p_api_sln_dir, const String &p_config); - static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type); - - static bool make_api_assembly(APIAssembly::Type p_api_type); - - static bool build_project_blocking(const String &p_config); - - static bool editor_build_callback(); - - GodotSharpBuilds(); - ~GodotSharpBuilds(); -}; - -#endif // GODOTSHARP_BUILDS_H diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp deleted file mode 100644 index 9d42528927..0000000000 --- a/modules/mono/editor/godotsharp_editor.cpp +++ /dev/null @@ -1,581 +0,0 @@ -/*************************************************************************/ -/* godotsharp_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "godotsharp_editor.h" - -#include "core/message_queue.h" -#include "core/os/os.h" -#include "core/project_settings.h" -#include "scene/gui/control.h" -#include "scene/main/node.h" - -#include "../csharp_script.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../utils/path_utils.h" -#include "bindings_generator.h" -#include "csharp_project.h" -#include "dotnet_solution.h" -#include "godotsharp_export.h" - -#ifdef OSX_ENABLED -#include "../utils/osx_utils.h" -#endif - -#ifdef WINDOWS_ENABLED -#include "../utils/mono_reg_utils.h" -#endif - -GodotSharpEditor *GodotSharpEditor::singleton = NULL; - -bool GodotSharpEditor::_create_project_solution() { - - EditorProgress pr("create_csharp_solution", TTR("Generating solution..."), 2); - - pr.step(TTR("Generating C# project...")); - - String path = OS::get_singleton()->get_resource_dir(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; - } - - String guid = CSharpProject::generate_game_project(path, name); - - if (guid.length()) { - - DotNetSolution solution(name); - - if (!solution.set_path(path)) { - show_error_dialog(TTR("Failed to create solution.")); - return false; - } - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = name + ".csproj"; - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - proj_info.configs.push_back("Tools"); - - solution.add_new_project(name, proj_info); - - Error sln_error = solution.save(); - - if (sln_error != OK) { - show_error_dialog(TTR("Failed to save solution.")); - return false; - } - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return false; - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return false; - - pr.step(TTR("Done")); - - // Here, after all calls to progress_task_step - call_deferred("_remove_create_sln_menu_option"); - - } else { - show_error_dialog(TTR("Failed to create C# project.")); - } - - return true; -} - -void GodotSharpEditor::_make_api_solutions_if_needed() { - // I'm sick entirely of ProgressDialog - - static int attempts_left = 100; - - if (MessageQueue::get_singleton()->is_flushing() || !SceneTree::get_singleton()) { - ERR_FAIL_COND(attempts_left == 0); // You've got to be kidding - - if (SceneTree::get_singleton()) { - SceneTree::get_singleton()->connect("idle_frame", this, "_make_api_solutions_if_needed", Vector<Variant>()); - } else { - call_deferred("_make_api_solutions_if_needed"); - } - - attempts_left--; - return; - } - - // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike - // the message queue, with signals the collateral damage should be minimal in the worst case. - static bool recursion_guard = false; - if (!recursion_guard) { - recursion_guard = true; - - // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead - SceneTree::get_singleton()->disconnect("idle_frame", this, "_make_api_solutions_if_needed"); - - _make_api_solutions_if_needed_impl(); - - recursion_guard = false; - } -} - -void GodotSharpEditor::_make_api_solutions_if_needed_impl() { - // If the project has a solution and C# project make sure the API assemblies are present and up to date - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - - if (!FileAccess::exists(res_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll")) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return; - } - - if (!FileAccess::exists(res_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll")) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return; // Redundant? I don't think so - } -} - -void GodotSharpEditor::_remove_create_sln_menu_option() { - - menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN)); - - bottom_panel_btn->show(); -} - -void GodotSharpEditor::_show_about_dialog() { - - bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start"); - about_dialog_checkbox->set_pressed(show_on_start); - about_dialog->popup_centered_minsize(); -} - -void GodotSharpEditor::_toggle_about_dialog_on_start(bool p_enabled) { - - bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start"); - if (show_on_start != p_enabled) { - EditorSettings::get_singleton()->set_setting("mono/editor/show_info_on_start", p_enabled); - } -} - -void GodotSharpEditor::_build_solution_pressed() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) { - if (!_create_project_solution()) - return; // Failed to create solution - } - - MonoBottomPanel::get_singleton()->call("_build_project_pressed"); -} - -void GodotSharpEditor::_menu_option_pressed(int p_id) { - - switch (p_id) { - case MENU_CREATE_SLN: { - - _create_project_solution(); - } break; - case MENU_ABOUT_CSHARP: { - - _show_about_dialog(); - } break; - default: - ERR_FAIL(); - } -} - -void GodotSharpEditor::_notification(int p_notification) { - - switch (p_notification) { - - case NOTIFICATION_READY: { - - bool show_info_dialog = EDITOR_GET("mono/editor/show_info_on_start"); - if (show_info_dialog) { - about_dialog->set_exclusive(true); - _show_about_dialog(); - // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive then. - about_dialog->set_exclusive(false); - } - } - } -} - -void GodotSharpEditor::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_build_solution_pressed"), &GodotSharpEditor::_build_solution_pressed); - ClassDB::bind_method(D_METHOD("_create_project_solution"), &GodotSharpEditor::_create_project_solution); - ClassDB::bind_method(D_METHOD("_make_api_solutions_if_needed"), &GodotSharpEditor::_make_api_solutions_if_needed); - ClassDB::bind_method(D_METHOD("_remove_create_sln_menu_option"), &GodotSharpEditor::_remove_create_sln_menu_option); - ClassDB::bind_method(D_METHOD("_toggle_about_dialog_on_start"), &GodotSharpEditor::_toggle_about_dialog_on_start); - ClassDB::bind_method(D_METHOD("_menu_option_pressed", "id"), &GodotSharpEditor::_menu_option_pressed); -} - -MonoBoolean godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled(MonoString *p_bundle_id) { -#ifdef OSX_ENABLED - return (MonoBoolean)osx_is_app_bundle_installed(GDMonoMarshal::mono_string_to_godot(p_bundle_id)); -#else - (void)p_bundle_id; // UNUSED - ERR_FAIL_V(false); -#endif -} - -MonoString *godot_icall_Utils_OS_GetPlatformName() { - return GDMonoMarshal::mono_string_from_godot(OS::get_singleton()->get_name()); -} - -void GodotSharpEditor::register_internal_calls() { - - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Editor.MonoDevelopInstance::IsApplicationBundleInstalled", (void *)godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled); - mono_add_internal_call("GodotSharpTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); - - GodotSharpBuilds::register_internal_calls(); - GodotSharpExport::register_internal_calls(); -} - -void GodotSharpEditor::show_error_dialog(const String &p_message, const String &p_title) { - - error_dialog->set_title(p_title); - error_dialog->set_text(p_message); - error_dialog->popup_centered_minsize(); -} - -Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { - - ExternalEditor editor = ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))); - - switch (editor) { - case EDITOR_VSCODE: { - static String vscode_path; - - if (vscode_path.empty() || !FileAccess::exists(vscode_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - bool found = false; - - // TODO: Use initializer lists once C++11 is allowed - - static Vector<String> vscode_names; - if (vscode_names.empty()) { - vscode_names.push_back("code"); - vscode_names.push_back("code-oss"); - vscode_names.push_back("vscode"); - vscode_names.push_back("vscode-oss"); - vscode_names.push_back("visual-studio-code"); - vscode_names.push_back("visual-studio-code-oss"); - } - for (int i = 0; i < vscode_names.size(); i++) { - vscode_path = path_which(vscode_names[i]); - if (!vscode_path.empty()) { - found = true; - break; - } - } - - if (!found) - vscode_path.clear(); // Not found, clear so next time the empty() check is enough - } - - List<String> args; - -#ifdef OSX_ENABLED - // The package path is '/Applications/Visual Studio Code.app' - static const String vscode_bundle_id = "com.microsoft.VSCode"; - static bool osx_app_bundle_installed = osx_is_app_bundle_installed(vscode_bundle_id); - - if (osx_app_bundle_installed) { - args.push_back("-b"); - args.push_back(vscode_bundle_id); - - // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is - // editing our folder. It's better to ask for a new window and let VSCode do the window management. - args.push_back("-n"); - - // The open process must wait until the application finishes (which is instant in VSCode's case) - args.push_back("--wait-apps"); - - args.push_back("--args"); - } -#endif - - args.push_back(ProjectSettings::get_singleton()->get_resource_path()); - - String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); - - if (p_line >= 0) { - args.push_back("-g"); - args.push_back(script_path + ":" + itos(p_line + 1) + ":" + itos(p_col)); - } else { - args.push_back(script_path); - } - -#ifdef OSX_ENABLED - ERR_EXPLAIN("Cannot find code editor: VSCode"); - ERR_FAIL_COND_V(!osx_app_bundle_installed && vscode_path.empty(), ERR_FILE_NOT_FOUND); - - String command = osx_app_bundle_installed ? "/usr/bin/open" : vscode_path; -#else - ERR_EXPLAIN("Cannot find code editor: VSCode"); - ERR_FAIL_COND_V(vscode_path.empty(), ERR_FILE_NOT_FOUND); - - String command = vscode_path; -#endif - - Error err = OS::get_singleton()->execute(command, args, false); - - if (err != OK) { - ERR_PRINT("Error when trying to execute code editor: VSCode"); - return err; - } - } break; -#ifdef OSX_ENABLED - case EDITOR_VISUALSTUDIO_MAC: - // [[fallthrough]]; -#endif - case EDITOR_MONODEVELOP: { -#ifdef OSX_ENABLED - bool is_visualstudio = editor == EDITOR_VISUALSTUDIO_MAC; - - MonoDevelopInstance **instance = is_visualstudio ? - &visualstudio_mac_instance : - &monodevelop_instance; - - MonoDevelopInstance::EditorId editor_id = is_visualstudio ? - MonoDevelopInstance::VISUALSTUDIO_FOR_MAC : - MonoDevelopInstance::MONODEVELOP; -#else - MonoDevelopInstance **instance = &monodevelop_instance; - MonoDevelopInstance::EditorId editor_id = MonoDevelopInstance::MONODEVELOP; -#endif - - if (!*instance) - *instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path(), editor_id)); - - String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); - - if (p_line >= 0) { - script_path += ";" + itos(p_line + 1) + ";" + itos(p_col); - } - - (*instance)->execute(script_path); - } break; - default: - return ERR_UNAVAILABLE; - } - - return OK; -} - -bool GodotSharpEditor::overrides_external_editor() { - - return ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))) != EDITOR_NONE; -} - -GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { - - singleton = this; - - monodevelop_instance = NULL; -#ifdef OSX_ENABLED - visualstudio_mac_instance = NULL; -#endif - - editor = p_editor; - - error_dialog = memnew(AcceptDialog); - editor->get_gui_base()->add_child(error_dialog); - - bottom_panel_btn = editor->add_bottom_panel_item(TTR("Mono"), memnew(MonoBottomPanel(editor))); - - godotsharp_builds = memnew(GodotSharpBuilds); - - editor->add_child(memnew(MonoReloadNode)); - - menu_popup = memnew(PopupMenu); - menu_popup->hide(); - menu_popup->set_as_toplevel(true); - menu_popup->set_pass_on_modal_close_click(false); - - editor->add_tool_submenu_item("Mono", menu_popup); - - // TODO: Remove or edit this info dialog once Mono support is no longer in alpha - { - menu_popup->add_item(TTR("About C# support"), MENU_ABOUT_CSHARP); - about_dialog = memnew(AcceptDialog); - editor->get_gui_base()->add_child(about_dialog); - about_dialog->set_title("Important: C# support is not feature-complete"); - - // We don't use set_text() as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox - // we'll add. Instead we add containers and a new autowrapped Label inside. - - // Main VBoxContainer (icon + label on top, checkbox at bottom) - VBoxContainer *about_vbc = memnew(VBoxContainer); - about_dialog->add_child(about_vbc); - - // HBoxContainer for icon + label - HBoxContainer *about_hbc = memnew(HBoxContainer); - about_vbc->add_child(about_hbc); - - TextureRect *about_icon = memnew(TextureRect); - about_hbc->add_child(about_icon); - Ref<Texture> about_icon_tex = about_icon->get_icon("NodeWarning", "EditorIcons"); - about_icon->set_texture(about_icon_tex); - - Label *about_label = memnew(Label); - about_hbc->add_child(about_label); - about_label->set_custom_minimum_size(Size2(600, 150) * EDSCALE); - about_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); - about_label->set_autowrap(true); - String about_text = - String("C# support in Godot Engine is in late alpha stage and, while already usable, ") + - "it is not meant for use in production.\n\n" + - "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + - "Bugs and usability issues will be addressed gradually over future releases, " + - "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + - "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + - " https://github.com/godotengine/godot/issues\n\n" + - "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; - about_label->set_text(about_text); - - EDITOR_DEF("mono/editor/show_info_on_start", true); - - // CheckBox in main container - about_dialog_checkbox = memnew(CheckBox); - about_vbc->add_child(about_dialog_checkbox); - about_dialog_checkbox->set_text("Show this warning when starting the editor"); - about_dialog_checkbox->connect("toggled", this, "_toggle_about_dialog_on_start"); - } - - String sln_path = GodotSharpDirs::get_project_sln_path(); - String csproj_path = GodotSharpDirs::get_project_csproj_path(); - - if (FileAccess::exists(sln_path) && FileAccess::exists(csproj_path)) { - // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized. - call_deferred("_make_api_solutions_if_needed"); - } else { - bottom_panel_btn->hide(); - menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN); - } - - menu_popup->connect("id_pressed", this, "_menu_option_pressed"); - - ToolButton *build_button = memnew(ToolButton); - build_button->set_text("Build"); - build_button->set_tooltip("Build solution"); - build_button->set_focus_mode(Control::FOCUS_NONE); - build_button->connect("pressed", this, "_build_solution_pressed"); - editor->get_menu_hb()->add_child(build_button); - - // External editor settings - EditorSettings *ed_settings = EditorSettings::get_singleton(); - EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE); - - String settings_hint_str = "Disabled"; - -#if defined(WINDOWS_ENABLED) - settings_hint_str += ",MonoDevelop,Visual Studio Code"; -#elif defined(OSX_ENABLED) - settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code"; -#elif defined(UNIX_ENABLED) - settings_hint_str += ",MonoDevelop,Visual Studio Code"; -#endif - - ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, settings_hint_str)); - - // Export plugin - Ref<GodotSharpExport> godotsharp_export; - godotsharp_export.instance(); - EditorExport::get_singleton()->add_export_plugin(godotsharp_export); -} - -GodotSharpEditor::~GodotSharpEditor() { - - singleton = NULL; - - memdelete(godotsharp_builds); - - if (monodevelop_instance) { - memdelete(monodevelop_instance); - monodevelop_instance = NULL; - } -} - -MonoReloadNode *MonoReloadNode::singleton = NULL; - -void MonoReloadNode::_reload_timer_timeout() { - - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } -} - -void MonoReloadNode::restart_reload_timer() { - - reload_timer->stop(); - reload_timer->start(); -} - -void MonoReloadNode::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_reload_timer_timeout"), &MonoReloadNode::_reload_timer_timeout); -} - -void MonoReloadNode::_notification(int p_what) { - switch (p_what) { - case MainLoop::NOTIFICATION_WM_FOCUS_IN: { - restart_reload_timer(); - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } - } break; - default: { - } break; - }; -} - -MonoReloadNode::MonoReloadNode() { - - singleton = this; - - reload_timer = memnew(Timer); - add_child(reload_timer); - reload_timer->set_one_shot(false); - reload_timer->set_wait_time(EDITOR_DEF("mono/assembly_watch_interval_sec", 0.5)); - reload_timer->connect("timeout", this, "_reload_timer_timeout"); - reload_timer->start(); -} - -MonoReloadNode::~MonoReloadNode() { - - singleton = NULL; -} diff --git a/modules/mono/editor/godotsharp_editor.h b/modules/mono/editor/godotsharp_editor.h deleted file mode 100644 index d9523c384c..0000000000 --- a/modules/mono/editor/godotsharp_editor.h +++ /dev/null @@ -1,134 +0,0 @@ -/*************************************************************************/ -/* godotsharp_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GODOTSHARP_EDITOR_H -#define GODOTSHARP_EDITOR_H - -#include "godotsharp_builds.h" -#include "monodevelop_instance.h" - -class GodotSharpEditor : public Node { - GDCLASS(GodotSharpEditor, Object) - - EditorNode *editor; - - MenuButton *menu_button; - PopupMenu *menu_popup; - - AcceptDialog *error_dialog; - AcceptDialog *about_dialog; - CheckBox *about_dialog_checkbox; - - ToolButton *bottom_panel_btn; - - GodotSharpBuilds *godotsharp_builds; - - MonoDevelopInstance *monodevelop_instance; -#ifdef OSX_ENABLED - MonoDevelopInstance *visualstudio_mac_instance; -#endif - - bool _create_project_solution(); - void _make_api_solutions_if_needed(); - void _make_api_solutions_if_needed_impl(); - - void _remove_create_sln_menu_option(); - void _show_about_dialog(); - void _toggle_about_dialog_on_start(bool p_enabled); - - void _menu_option_pressed(int p_id); - - void _build_solution_pressed(); - - static GodotSharpEditor *singleton; - -protected: - void _notification(int p_notification); - static void _bind_methods(); - -public: - enum MenuOptions { - MENU_CREATE_SLN, - MENU_ABOUT_CSHARP, - }; - - enum ExternalEditor { - EDITOR_NONE, -#if defined(WINDOWS_ENABLED) - //EDITOR_VISUALSTUDIO, // TODO - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#elif defined(OSX_ENABLED) - EDITOR_VISUALSTUDIO_MAC, - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#elif defined(UNIX_ENABLED) - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#endif - }; - - _FORCE_INLINE_ static GodotSharpEditor *get_singleton() { return singleton; } - - static void register_internal_calls(); - - void show_error_dialog(const String &p_message, const String &p_title = "Error"); - - Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col); - bool overrides_external_editor(); - - GodotSharpEditor(EditorNode *p_editor); - ~GodotSharpEditor(); -}; - -class MonoReloadNode : public Node { - GDCLASS(MonoReloadNode, Node) - - Timer *reload_timer; - - void _reload_timer_timeout(); - - static MonoReloadNode *singleton; - -protected: - static void _bind_methods(); - - void _notification(int p_what); - -public: - _FORCE_INLINE_ static MonoReloadNode *get_singleton() { return singleton; } - - void restart_reload_timer(); - - MonoReloadNode(); - ~MonoReloadNode(); -}; - -#endif // GODOTSHARP_EDITOR_H diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index ee5fed1a0c..020bb70a08 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -30,164 +30,28 @@ #include "godotsharp_export.h" -#include "core/version.h" - -#include "../csharp_script.h" -#include "../godotsharp_defs.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "csharp_project.h" -#include "godotsharp_builds.h" - -static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() { - String current_version = VERSION_FULL_CONFIG; - String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version); - return GDMonoMarshal::mono_string_from_godot(ProjectSettings::get_singleton()->globalize_path(templates_dir)); -} - -static MonoString *godot_icall_GodotSharpExport_GetDataDirName() { - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - return GDMonoMarshal::mono_string_from_godot("data_" + appname_safe); -} +#include <mono/metadata/image.h> -void GodotSharpExport::register_internal_calls() { - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; +#include "../mono_gd/gd_mono.h" +#include "../mono_gd/gd_mono_assembly.h" - mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetTemplatesDir", (void *)godot_icall_GodotSharpExport_GetTemplatesDir); - mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetDataDirName", (void *)godot_icall_GodotSharpExport_GetDataDirName); -} +String get_assemblyref_name(MonoImage *p_image, int index) { + const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); -void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &) { + uint32_t cols[MONO_ASSEMBLYREF_SIZE]; - if (p_type != CSharpLanguage::get_singleton()->get_type()) - return; + mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE); - ERR_FAIL_COND(p_path.get_extension() != CSharpLanguage::get_singleton()->get_extension()); - - // TODO what if the source file is not part of the game's C# project - - if (!GLOBAL_GET("mono/export/include_scripts_content")) { - // We don't want to include the source code on exported games - add_file(p_path, Vector<uint8_t>(), false); - skip(); - } + return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])); } -void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) { - - // TODO right now there is no way to stop the export process with an error - - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - ERR_FAIL_NULL(TOOLS_DOMAIN); - ERR_FAIL_NULL(GDMono::get_singleton()->get_editor_tools_assembly()); - - if (FileAccess::exists(GodotSharpDirs::get_project_sln_path())) { - String build_config = p_debug ? "Debug" : "Release"; - - String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release")); - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path); - ERR_FAIL_COND(metadata_err != OK); - - ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path)); - - ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config)); - - // Add dependency assemblies - - Map<String, String> dependencies; - - String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name"); - if (project_dll_name.empty()) { - project_dll_name = "UnnamedProject"; - } - - String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config); - String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name + ".dll"); - dependencies.insert(project_dll_name, project_dll_src_path); - - { - MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); - ERR_FAIL_NULL(export_domain); - _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); - - _GDMONO_SCOPE_DOMAIN_(export_domain); - - GDMonoAssembly *scripts_assembly = NULL; - bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name, - project_dll_src_path, &scripts_assembly, /* refonly: */ true); - - ERR_EXPLAIN("Cannot load refonly assembly: " + project_dll_name); - ERR_FAIL_COND(!load_success); - - Vector<String> search_dirs; - GDMonoAssembly::fill_search_dirs(search_dirs, build_config); - Error depend_error = _get_assembly_dependencies(scripts_assembly, search_dirs, dependencies); - ERR_FAIL_COND(depend_error != OK); - } - - for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) { - String depend_src_path = E->value(); - String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file()); - ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path)); - } - } - - // Mono specific export template extras (data dir) - - GDMonoClass *export_class = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "GodotSharpExport"); - ERR_FAIL_NULL(export_class); - GDMonoMethod *export_begin_method = export_class->get_method("_ExportBegin", 4); - ERR_FAIL_NULL(export_begin_method); - - MonoArray *features = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_features.size()); - int i = 0; - for (const Set<String>::Element *E = p_features.front(); E; E = E->next()) { - MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get()); - mono_array_set(features, MonoString *, i, boxed); - i++; - } - - MonoBoolean debug = p_debug; - MonoString *path = GDMonoMarshal::mono_string_from_godot(p_path); - uint32_t flags = p_flags; - void *args[4] = { features, &debug, path, &flags }; - MonoException *exc = NULL; - export_begin_method->invoke_raw(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } -} - -bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) { - - FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ); - ERR_FAIL_COND_V(!f, false); - - Vector<uint8_t> data; - data.resize(f->get_len()); - f->get_buffer(data.ptrw(), data.size()); - - add_file(p_dst_path, data, p_remap); - - return true; -} - -Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies) { - +Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) { MonoImage *image = p_assembly->get_image(); for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { - MonoAssemblyName *ref_aname = aname_prealloc; - mono_assembly_get_assemblyref(image, i, ref_aname); - String ref_name = mono_assembly_name_get_name(ref_aname); + String ref_name = get_assemblyref_name(image, i); - if (r_dependencies.find(ref_name)) + if (r_dependencies.has(ref_name)) continue; GDMonoAssembly *ref_assembly = NULL; @@ -226,24 +90,34 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c ERR_FAIL_V(ERR_CANT_RESOLVE); } - r_dependencies.insert(ref_name, ref_assembly->get_path()); + r_dependencies[ref_name] = ref_assembly->get_path(); - Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); - if (err != OK) - return err; + Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); + if (err != OK) { + ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name); + ERR_FAIL_V(err); + } } return OK; } -GodotSharpExport::GodotSharpExport() { - // MonoAssemblyName is an incomplete type (internal to mono), so we can't allocate it ourselves. - // There isn't any api to allocate an empty one either, so we need to do it this way. - aname_prealloc = mono_assembly_name_new("whatever"); - mono_assembly_name_free(aname_prealloc); // "it does not frees the object itself, only the name members" (typo included) -} +Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) { + MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); + ERR_FAIL_NULL_V(export_domain, FAILED); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); + + _GDMONO_SCOPE_DOMAIN_(export_domain); + + GDMonoAssembly *scripts_assembly = NULL; + bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name, + p_project_dll_src_path, &scripts_assembly, /* refonly: */ true); + + ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name); + ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE); + + Vector<String> search_dirs; + GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir); -GodotSharpExport::~GodotSharpExport() { - if (aname_prealloc) - mono_free(aname_prealloc); + return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies); } diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index 4dc8ea75d5..8d121a6bc3 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -31,29 +31,19 @@ #ifndef GODOTSHARP_EXPORT_H #define GODOTSHARP_EXPORT_H -#include <mono/metadata/image.h> - -#include "editor/editor_export.h" +#include "core/dictionary.h" +#include "core/error_list.h" +#include "core/ustring.h" #include "../mono_gd/gd_mono_header.h" -class GodotSharpExport : public EditorExportPlugin { - - MonoAssemblyName *aname_prealloc; - - bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false); - - Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies); - -protected: - virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features); - virtual void _export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags); +namespace GodotSharpExport { -public: - static void register_internal_calls(); +Error get_exported_assembly_dependencies(const String &p_project_dll_name, + const String &p_project_dll_src_path, const String &p_build_config, + const String &p_custom_lib_dir, Dictionary &r_dependencies); +Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); - GodotSharpExport(); - ~GodotSharpExport(); -}; +} // namespace GodotSharpExport #endif // GODOTSHARP_EXPORT_H diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp deleted file mode 100644 index 21ce9ca5c4..0000000000 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ /dev/null @@ -1,525 +0,0 @@ -/*************************************************************************/ -/* mono_bottom_panel.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "mono_bottom_panel.h" - -#include "editor/plugins/script_editor_plugin.h" -#include "editor/script_editor_debugger.h" - -#include "../csharp_script.h" -#include "../godotsharp_dirs.h" -#include "csharp_project.h" -#include "godotsharp_editor.h" - -MonoBottomPanel *MonoBottomPanel::singleton = NULL; - -void MonoBottomPanel::_update_build_tabs_list() { - - build_tabs_list->clear(); - - int current_tab = build_tabs->get_current_tab(); - - bool no_current_tab = current_tab < 0 || current_tab >= build_tabs->get_tab_count(); - - for (int i = 0; i < build_tabs->get_child_count(); i++) { - - MonoBuildTab *tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(i)); - - if (tab) { - String item_name = tab->build_info.solution.get_file().get_basename(); - item_name += " [" + tab->build_info.configuration + "]"; - - build_tabs_list->add_item(item_name, tab->get_icon_texture()); - - String item_tooltip = "Solution: " + tab->build_info.solution; - item_tooltip += "\nConfiguration: " + tab->build_info.configuration; - item_tooltip += "\nStatus: "; - - if (tab->build_exited) { - item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored"; - } else { - item_tooltip += "Running"; - } - - if (!tab->build_exited || tab->build_result == MonoBuildTab::RESULT_ERROR) { - item_tooltip += "\nErrors: " + itos(tab->error_count); - } - - item_tooltip += "\nWarnings: " + itos(tab->warning_count); - - build_tabs_list->set_item_tooltip(i, item_tooltip); - - if (no_current_tab || current_tab == i) { - build_tabs_list->select(i); - _build_tabs_item_selected(i); - } - } - } -} - -void MonoBottomPanel::add_build_tab(MonoBuildTab *p_build_tab) { - - build_tabs->add_child(p_build_tab); - raise_build_tab(p_build_tab); -} - -void MonoBottomPanel::raise_build_tab(MonoBuildTab *p_build_tab) { - - ERR_FAIL_COND(p_build_tab->get_parent() != build_tabs); - build_tabs->move_child(p_build_tab, 0); - _update_build_tabs_list(); -} - -void MonoBottomPanel::show_build_tab() { - - for (int i = 0; i < panel_tabs->get_tab_count(); i++) { - if (panel_tabs->get_tab_control(i) == panel_builds_tab) { - panel_tabs->set_current_tab(i); - editor->make_bottom_panel_item_visible(this); - return; - } - } - - ERR_PRINT("Builds tab not found"); -} - -void MonoBottomPanel::_build_tabs_item_selected(int p_idx) { - - ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count()); - - build_tabs->set_current_tab(p_idx); - if (!build_tabs->is_visible()) - build_tabs->set_visible(true); - - warnings_btn->set_visible(true); - errors_btn->set_visible(true); - view_log_btn->set_visible(true); -} - -void MonoBottomPanel::_build_tabs_nothing_selected() { - - if (build_tabs->get_tab_count() != 0) { // just in case - build_tabs->set_visible(false); - - // This callback is called when clicking on the empty space of the list. - // ItemList won't deselect the items automatically, so we must do it ourselves. - build_tabs_list->unselect_all(); - } - - warnings_btn->set_visible(false); - errors_btn->set_visible(false); - view_log_btn->set_visible(false); -} - -void MonoBottomPanel::_warnings_toggled(bool p_pressed) { - - int current_tab = build_tabs->get_current_tab(); - ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); - build_tab->warnings_visible = p_pressed; - build_tab->_update_issues_list(); -} - -void MonoBottomPanel::_errors_toggled(bool p_pressed) { - - int current_tab = build_tabs->get_current_tab(); - ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); - build_tab->errors_visible = p_pressed; - build_tab->_update_issues_list(); -} - -void MonoBottomPanel::_build_project_pressed() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return; // No solution to build - - String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); - String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player"); - - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor); - ERR_FAIL_COND(metadata_err != OK); - - if (FileAccess::exists(scripts_metadata_path_editor)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player); - - ERR_EXPLAIN("Failed to copy scripts metadata file"); - ERR_FAIL_COND(copy_err != OK); - } - - bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools"); - - if (build_success) { - // Notify running game for hot-reload - ScriptEditor::get_singleton()->get_debugger()->reload_scripts(); - - // Hot-reload in the editor - MonoReloadNode::get_singleton()->restart_reload_timer(); - - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } - } -} - -void MonoBottomPanel::_view_log_pressed() { - - if (build_tabs_list->is_anything_selected()) { - Vector<int> selected_items = build_tabs_list->get_selected_items(); - CRASH_COND(selected_items.size() != 1); - int selected_item = selected_items[0]; - - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_tab_control(selected_item)); - ERR_FAIL_NULL(build_tab); - - String log_dirpath = build_tab->get_build_info().get_log_dirpath(); - - OS::get_singleton()->shell_open(log_dirpath.plus_file(GodotSharpBuilds::get_msbuild_log_filename())); - } -} - -void MonoBottomPanel::_notification(int p_what) { - - switch (p_what) { - - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); - panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); - panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); - } break; - } -} - -void MonoBottomPanel::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_build_project_pressed"), &MonoBottomPanel::_build_project_pressed); - ClassDB::bind_method(D_METHOD("_view_log_pressed"), &MonoBottomPanel::_view_log_pressed); - ClassDB::bind_method(D_METHOD("_warnings_toggled", "pressed"), &MonoBottomPanel::_warnings_toggled); - ClassDB::bind_method(D_METHOD("_errors_toggled", "pressed"), &MonoBottomPanel::_errors_toggled); - ClassDB::bind_method(D_METHOD("_build_tabs_item_selected", "idx"), &MonoBottomPanel::_build_tabs_item_selected); - ClassDB::bind_method(D_METHOD("_build_tabs_nothing_selected"), &MonoBottomPanel::_build_tabs_nothing_selected); -} - -MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) { - - singleton = this; - - editor = p_editor; - - set_v_size_flags(SIZE_EXPAND_FILL); - set_anchors_and_margins_preset(Control::PRESET_WIDE); - - panel_tabs = memnew(TabContainer); - panel_tabs->set_tab_align(TabContainer::ALIGN_LEFT); - panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); - panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); - panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); - panel_tabs->set_custom_minimum_size(Size2(0, 228) * EDSCALE); - panel_tabs->set_v_size_flags(SIZE_EXPAND_FILL); - add_child(panel_tabs); - - { // Builds - panel_builds_tab = memnew(VBoxContainer); - panel_builds_tab->set_name(TTR("Builds")); - panel_builds_tab->set_h_size_flags(SIZE_EXPAND_FILL); - panel_tabs->add_child(panel_builds_tab); - - HBoxContainer *toolbar_hbc = memnew(HBoxContainer); - toolbar_hbc->set_h_size_flags(SIZE_EXPAND_FILL); - panel_builds_tab->add_child(toolbar_hbc); - - Button *build_project_btn = memnew(Button); - build_project_btn->set_text(TTR("Build Project")); - build_project_btn->set_focus_mode(FOCUS_NONE); - build_project_btn->connect("pressed", this, "_build_project_pressed"); - toolbar_hbc->add_child(build_project_btn); - - toolbar_hbc->add_spacer(); - - warnings_btn = memnew(ToolButton); - warnings_btn->set_text(TTR("Warnings")); - warnings_btn->set_toggle_mode(true); - warnings_btn->set_pressed(true); - warnings_btn->set_visible(false); - warnings_btn->set_focus_mode(FOCUS_NONE); - warnings_btn->connect("toggled", this, "_warnings_toggled"); - toolbar_hbc->add_child(warnings_btn); - - errors_btn = memnew(ToolButton); - errors_btn->set_text(TTR("Errors")); - errors_btn->set_toggle_mode(true); - errors_btn->set_pressed(true); - errors_btn->set_visible(false); - errors_btn->set_focus_mode(FOCUS_NONE); - errors_btn->connect("toggled", this, "_errors_toggled"); - toolbar_hbc->add_child(errors_btn); - - toolbar_hbc->add_spacer(); - - view_log_btn = memnew(Button); - view_log_btn->set_text(TTR("View log")); - view_log_btn->set_focus_mode(FOCUS_NONE); - view_log_btn->set_visible(false); - view_log_btn->connect("pressed", this, "_view_log_pressed"); - toolbar_hbc->add_child(view_log_btn); - - HSplitContainer *hsc = memnew(HSplitContainer); - hsc->set_h_size_flags(SIZE_EXPAND_FILL); - hsc->set_v_size_flags(SIZE_EXPAND_FILL); - panel_builds_tab->add_child(hsc); - - build_tabs_list = memnew(ItemList); - build_tabs_list->set_h_size_flags(SIZE_EXPAND_FILL); - build_tabs_list->connect("item_selected", this, "_build_tabs_item_selected"); - build_tabs_list->connect("nothing_selected", this, "_build_tabs_nothing_selected"); - hsc->add_child(build_tabs_list); - - build_tabs = memnew(TabContainer); - build_tabs->set_tab_align(TabContainer::ALIGN_LEFT); - build_tabs->set_h_size_flags(SIZE_EXPAND_FILL); - build_tabs->set_tabs_visible(false); - hsc->add_child(build_tabs); - } -} - -MonoBottomPanel::~MonoBottomPanel() { - - singleton = NULL; -} - -void MonoBuildTab::_load_issues_from_file(const String &p_csv_file) { - - FileAccessRef f = FileAccess::open(p_csv_file, FileAccess::READ); - - if (!f) - return; - - while (!f->eof_reached()) { - Vector<String> csv_line = f->get_csv_line(); - - if (csv_line.size() == 1 && csv_line[0].empty()) - return; - - ERR_CONTINUE(csv_line.size() != 7); - - BuildIssue issue; - issue.warning = csv_line[0] == "warning"; - issue.file = csv_line[1]; - issue.line = csv_line[2].to_int(); - issue.column = csv_line[3].to_int(); - issue.code = csv_line[4]; - issue.message = csv_line[5]; - issue.project_file = csv_line[6]; - - if (issue.warning) - warning_count += 1; - else - error_count += 1; - - issues.push_back(issue); - } -} - -void MonoBuildTab::_update_issues_list() { - - issues_list->clear(); - - Ref<Texture> warning_icon = get_icon("Warning", "EditorIcons"); - Ref<Texture> error_icon = get_icon("Error", "EditorIcons"); - - for (int i = 0; i < issues.size(); i++) { - - const BuildIssue &issue = issues[i]; - - if (!(issue.warning ? warnings_visible : errors_visible)) - continue; - - String tooltip; - tooltip += String("Message: ") + issue.message; - - if (issue.code.length()) { - tooltip += String("\nCode: ") + issue.code; - } - - tooltip += String("\nType: ") + (issue.warning ? "warning" : "error"); - - String text; - - if (issue.file.length()) { - String sline = String::num_int64(issue.line); - String scolumn = String::num_int64(issue.column); - - text += issue.file + "("; - text += sline + ","; - text += scolumn + "): "; - - tooltip += "\nFile: " + issue.file; - tooltip += "\nLine: " + sline; - tooltip += "\nColumn: " + scolumn; - } - - if (issue.project_file.length()) { - tooltip += "\nProject: " + issue.project_file; - } - - text += issue.message; - - int line_break_idx = text.find("\n"); - issues_list->add_item(line_break_idx == -1 ? text : text.substr(0, line_break_idx), - issue.warning ? warning_icon : error_icon); - int index = issues_list->get_item_count() - 1; - issues_list->set_item_tooltip(index, tooltip); - issues_list->set_item_metadata(index, i); - } -} - -Ref<Texture> MonoBuildTab::get_icon_texture() const { - - if (build_exited) { - if (build_result == RESULT_ERROR) { - return get_icon("StatusError", "EditorIcons"); - } else { - return get_icon("StatusSuccess", "EditorIcons"); - } - } else { - return get_icon("Stop", "EditorIcons"); - } -} - -MonoBuildInfo MonoBuildTab::get_build_info() { - - return build_info; -} - -void MonoBuildTab::on_build_start() { - - build_exited = false; - - issues.clear(); - warning_count = 0; - error_count = 0; - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::on_build_exit(BuildResult result) { - - build_exited = true; - build_result = result; - - _load_issues_from_file(logs_dir.plus_file(GodotSharpBuilds::get_msbuild_issues_filename())); - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::on_build_exec_failed(const String &p_cause) { - - build_exited = true; - build_result = RESULT_ERROR; - - issues_list->clear(); - - BuildIssue issue; - issue.message = p_cause; - issue.warning = false; - - error_count += 1; - issues.push_back(issue); - - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::restart_build() { - - ERR_FAIL_COND(!build_exited); - GodotSharpBuilds::get_singleton()->restart_build(this); -} - -void MonoBuildTab::stop_build() { - - ERR_FAIL_COND(build_exited); - GodotSharpBuilds::get_singleton()->stop_build(this); -} - -void MonoBuildTab::_issue_activated(int p_idx) { - - ERR_FAIL_INDEX(p_idx, issues_list->get_item_count()); - - // Get correct issue idx from issue list - int issue_idx = this->issues_list->get_item_metadata(p_idx); - - ERR_FAIL_INDEX(issue_idx, issues.size()); - - const BuildIssue &issue = issues[issue_idx]; - - if (issue.project_file.empty() && issue.file.empty()) - return; - - String project_dir = issue.project_file.length() ? issue.project_file.get_base_dir() : build_info.solution.get_base_dir(); - - String file = project_dir.simplify_path().plus_file(issue.file.simplify_path()); - - if (!FileAccess::exists(file)) - return; - - file = ProjectSettings::get_singleton()->localize_path(file); - - if (file.begins_with("res://")) { - Ref<Script> script = ResourceLoader::load(file, CSharpLanguage::get_singleton()->get_type()); - - if (script.is_valid() && ScriptEditor::get_singleton()->edit(script, issue.line, issue.column)) { - EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); - } - } -} - -void MonoBuildTab::_bind_methods() { - - ClassDB::bind_method("_issue_activated", &MonoBuildTab::_issue_activated); -} - -MonoBuildTab::MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir) : - build_exited(false), - issues_list(memnew(ItemList)), - error_count(0), - warning_count(0), - errors_visible(true), - warnings_visible(true), - logs_dir(p_logs_dir), - build_info(p_build_info) { - issues_list->set_v_size_flags(SIZE_EXPAND_FILL); - issues_list->connect("item_activated", this, "_issue_activated"); - add_child(issues_list); -} diff --git a/modules/mono/editor/mono_bottom_panel.h b/modules/mono/editor/mono_bottom_panel.h deleted file mode 100644 index 406e46f7ce..0000000000 --- a/modules/mono/editor/mono_bottom_panel.h +++ /dev/null @@ -1,150 +0,0 @@ -/*************************************************************************/ -/* mono_bottom_panel.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MONO_BOTTOM_PANEL_H -#define MONO_BOTTOM_PANEL_H - -#include "editor/editor_node.h" -#include "scene/gui/control.h" - -#include "mono_build_info.h" - -class MonoBuildTab; - -class MonoBottomPanel : public VBoxContainer { - - GDCLASS(MonoBottomPanel, VBoxContainer) - - EditorNode *editor; - - TabContainer *panel_tabs; - - VBoxContainer *panel_builds_tab; - - ItemList *build_tabs_list; - TabContainer *build_tabs; - - ToolButton *warnings_btn; - ToolButton *errors_btn; - Button *view_log_btn; - - void _update_build_tabs_list(); - - void _build_tabs_item_selected(int p_idx); - void _build_tabs_nothing_selected(); - - void _warnings_toggled(bool p_pressed); - void _errors_toggled(bool p_pressed); - - void _build_project_pressed(); - void _view_log_pressed(); - - static MonoBottomPanel *singleton; - -protected: - void _notification(int p_what); - - static void _bind_methods(); - -public: - _FORCE_INLINE_ static MonoBottomPanel *get_singleton() { return singleton; } - - void add_build_tab(MonoBuildTab *p_build_tab); - void raise_build_tab(MonoBuildTab *p_build_tab); - - void show_build_tab(); - - MonoBottomPanel(EditorNode *p_editor = NULL); - ~MonoBottomPanel(); -}; - -class MonoBuildTab : public VBoxContainer { - - GDCLASS(MonoBuildTab, VBoxContainer) - -public: - enum BuildResult { - RESULT_ERROR, - RESULT_SUCCESS - }; - - struct BuildIssue { - bool warning; - String file; - int line; - int column; - String code; - String message; - String project_file; - }; - -private: - friend class MonoBottomPanel; - - bool build_exited; - BuildResult build_result; - - Vector<BuildIssue> issues; - ItemList *issues_list; - - int error_count; - int warning_count; - - bool errors_visible; - bool warnings_visible; - - String logs_dir; - - MonoBuildInfo build_info; - - void _load_issues_from_file(const String &p_csv_file); - void _update_issues_list(); - - void _issue_activated(int p_idx); - -protected: - static void _bind_methods(); - -public: - Ref<Texture> get_icon_texture() const; - - MonoBuildInfo get_build_info(); - - void on_build_start(); - void on_build_exit(BuildResult result); - void on_build_exec_failed(const String &p_cause); - - void restart_build(); - void stop_build(); - - MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir); -}; - -#endif // MONO_BOTTOM_PANEL_H diff --git a/modules/mono/editor/mono_build_info.cpp b/modules/mono/editor/mono_build_info.cpp deleted file mode 100644 index b386c06435..0000000000 --- a/modules/mono/editor/mono_build_info.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/*************************************************************************/ -/* mono_build_info.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "mono_build_info.h" - -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_utils.h" - -uint32_t MonoBuildInfo::Hasher::hash(const MonoBuildInfo &p_key) { - - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.solution.hash()); - GDMonoUtils::hash_combine(hash, p_key.configuration.hash()); - - return hash; -} - -bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const { - - return p_b.solution == solution && p_b.configuration == configuration; -} - -String MonoBuildInfo::get_log_dirpath() { - - return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration); -} - -MonoBuildInfo::MonoBuildInfo() {} - -MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) { - - solution = p_solution; - configuration = p_config; -} diff --git a/modules/mono/glue/Managed/.gitignore b/modules/mono/glue/Managed/.gitignore new file mode 100644 index 0000000000..146421cac8 --- /dev/null +++ b/modules/mono/glue/Managed/.gitignore @@ -0,0 +1,2 @@ +# Generated Godot API solution folder +Generated diff --git a/modules/mono/glue/Managed/Files/AABB.cs b/modules/mono/glue/Managed/Files/AABB.cs index 33b2b46712..a2ebbc0736 100644 --- a/modules/mono/glue/Managed/Files/AABB.cs +++ b/modules/mono/glue/Managed/Files/AABB.cs @@ -414,6 +414,21 @@ namespace Godot _position = position; _size = size; } + public AABB(Vector3 position, real_t width, real_t height, real_t depth) + { + _position = position; + _size = new Vector3(width, height, depth); + } + public AABB(real_t x, real_t y, real_t z, Vector3 size) + { + _position = new Vector3(x, y, z); + _size = size; + } + public AABB(real_t x, real_t y, real_t z, real_t width, real_t height, real_t depth) + { + _position = new Vector3(x, y, z); + _size = new Vector3(width, height, depth); + } public static bool operator ==(AABB left, AABB right) { diff --git a/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs index 2398e10135..1bf6d5199a 100644 --- a/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs +++ b/modules/mono/glue/Managed/Files/Attributes/RPCAttributes.cs @@ -2,27 +2,27 @@ using System; namespace Godot { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class RemoteAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class SyncAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class MasterAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class PuppetAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class SlaveAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class RemoteSyncAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class MasterSyncAttribute : Attribute {} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public class PuppetSyncAttribute : Attribute {} } diff --git a/modules/mono/glue/Managed/Files/Basis.cs b/modules/mono/glue/Managed/Files/Basis.cs index ac9576cebd..9cc31a0557 100644 --- a/modules/mono/glue/Managed/Files/Basis.cs +++ b/modules/mono/glue/Managed/Files/Basis.cs @@ -260,13 +260,13 @@ namespace Godot Vector3 euler; euler.z = 0.0f; - real_t mxy = m.Row1[2]; + real_t mzy = m.Row1[2]; - if (mxy < 1.0f) + if (mzy < 1.0f) { - if (mxy > -1.0f) + if (mzy > -1.0f) { - euler.x = Mathf.Asin(-mxy); + euler.x = Mathf.Asin(-mzy); euler.y = Mathf.Atan2(m.Row0[2], m.Row2[2]); euler.z = Mathf.Atan2(m.Row1[0], m.Row1[1]); } @@ -418,19 +418,11 @@ namespace Godot public Basis Scaled(Vector3 scale) { - var m = this; - - m.Row0[0] *= scale.x; - m.Row0[1] *= scale.x; - m.Row0[2] *= scale.x; - m.Row1[0] *= scale.y; - m.Row1[1] *= scale.y; - m.Row1[2] *= scale.y; - m.Row2[0] *= scale.z; - m.Row2[1] *= scale.z; - m.Row2[2] *= scale.z; - - return m; + var b = this; + b.Row0 *= scale.x; + b.Row1 *= scale.y; + b.Row2 *= scale.z; + return b; } public real_t Tdotx(Vector3 with) @@ -583,31 +575,29 @@ namespace Godot public Basis(Vector3 axis, real_t phi) { - var axis_sq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z); - + Vector3 axisSq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z); real_t cosine = Mathf.Cos(phi); + Row0.x = axisSq.x + cosine * (1.0f - axisSq.x); + Row1.y = axisSq.y + cosine * (1.0f - axisSq.y); + Row2.z = axisSq.z + cosine * (1.0f - axisSq.z); + real_t sine = Mathf.Sin(phi); + real_t t = 1.0f - cosine; - Row0 = new Vector3 - ( - axis_sq.x + cosine * (1.0f - axis_sq.x), - axis.x * axis.y * (1.0f - cosine) - axis.z * sine, - axis.z * axis.x * (1.0f - cosine) + axis.y * sine - ); + real_t xyzt = axis.x * axis.y * t; + real_t zyxs = axis.z * sine; + Row0.y = xyzt - zyxs; + Row1.x = xyzt + zyxs; - Row1 = new Vector3 - ( - axis.x * axis.y * (1.0f - cosine) + axis.z * sine, - axis_sq.y + cosine * (1.0f - axis_sq.y), - axis.y * axis.z * (1.0f - cosine) - axis.x * sine - ); + xyzt = axis.x * axis.z * t; + zyxs = axis.y * sine; + Row0.z = xyzt + zyxs; + Row2.x = xyzt - zyxs; - Row2 = new Vector3 - ( - axis.z * axis.x * (1.0f - cosine) - axis.y * sine, - axis.y * axis.z * (1.0f - cosine) + axis.x * sine, - axis_sq.z + cosine * (1.0f - axis_sq.z) - ); + xyzt = axis.y * axis.z * t; + zyxs = axis.x * sine; + Row1.z = xyzt - zyxs; + Row2.y = xyzt + zyxs; } public Basis(Vector3 column0, Vector3 column1, Vector3 column2) @@ -622,11 +612,12 @@ namespace Godot // We need to assign the struct fields here first so we can't do it that way... } - internal Basis(real_t xx, real_t xy, real_t xz, real_t yx, real_t yy, real_t yz, real_t zx, real_t zy, real_t zz) + // Arguments are named such that xy is equal to calling x.y + internal Basis(real_t xx, real_t yx, real_t zx, real_t xy, real_t yy, real_t zy, real_t xz, real_t yz, real_t zz) { - Row0 = new Vector3(xx, xy, xz); - Row1 = new Vector3(yx, yy, yz); - Row2 = new Vector3(zx, zy, zz); + Row0 = new Vector3(xx, yx, zx); + Row1 = new Vector3(xy, yy, zy); + Row2 = new Vector3(xz, yz, zz); } public static Basis operator *(Basis left, Basis right) diff --git a/modules/mono/glue/Managed/Files/Color.cs b/modules/mono/glue/Managed/Files/Color.cs index 88fa3323c2..84ff19fc54 100644 --- a/modules/mono/glue/Managed/Files/Color.cs +++ b/modules/mono/glue/Managed/Files/Color.cs @@ -168,7 +168,7 @@ namespace Godot int max = Mathf.Max(color.r8, Mathf.Max(color.g8, color.b8)); int min = Mathf.Min(color.r8, Mathf.Min(color.g8, color.b8)); - float delta = max - min; + int delta = max - min; if (delta == 0) { @@ -591,11 +591,11 @@ namespace Godot public static bool operator <(Color left, Color right) { - if (left.r == right.r) + if (Mathf.IsEqualApprox(left.r, right.r)) { - if (left.g == right.g) + if (Mathf.IsEqualApprox(left.g, right.g)) { - if (left.b == right.b) + if (Mathf.IsEqualApprox(left.b, right.b)) return left.a < right.a; return left.b < right.b; } @@ -608,11 +608,11 @@ namespace Godot public static bool operator >(Color left, Color right) { - if (left.r == right.r) + if (Mathf.IsEqualApprox(left.r, right.r)) { - if (left.g == right.g) + if (Mathf.IsEqualApprox(left.g, right.g)) { - if (left.b == right.b) + if (Mathf.IsEqualApprox(left.b, right.b)) return left.a > right.a; return left.b > right.b; } @@ -635,7 +635,7 @@ namespace Godot public bool Equals(Color other) { - return r == other.r && g == other.g && b == other.b && a == other.a; + return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a); } public override int GetHashCode() diff --git a/modules/mono/glue/Managed/Files/DynamicObject.cs b/modules/mono/glue/Managed/Files/DynamicObject.cs index 9860feafdd..a0f105d55e 100644 --- a/modules/mono/glue/Managed/Files/DynamicObject.cs +++ b/modules/mono/glue/Managed/Files/DynamicObject.cs @@ -202,7 +202,7 @@ namespace Godot //public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes); //public override bool TryDeleteMember(DeleteMemberBinder binder); - // Invokation on the object itself, e.g.: obj(param) + // Invocation on the object itself, e.g.: obj(param) //public override bool TryInvoke(InvokeBinder binder, object[] args, out object result); // No unnary operations to handle diff --git a/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs index 366d89b1c2..5023725f17 100644 --- a/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs +++ b/modules/mono/glue/Managed/Files/Extensions/NodeExtensions.cs @@ -24,12 +24,12 @@ namespace Godot public T GetOwner<T>() where T : class { - return (T)(object)GetOwner(); + return (T)(object)Owner; } public T GetOwnerOrNull<T>() where T : class { - return GetOwner() as T; + return Owner as T; } public T GetParent<T>() where T : class diff --git a/modules/mono/glue/Managed/Files/GD.cs b/modules/mono/glue/Managed/Files/GD.cs index d968f8a78f..2068099ac6 100644 --- a/modules/mono/glue/Managed/Files/GD.cs +++ b/modules/mono/glue/Managed/Files/GD.cs @@ -83,7 +83,7 @@ namespace Godot public static void Print(params object[] what) { - godot_icall_GD_print(what); + godot_icall_GD_print(Array.ConvertAll(what, x => x.ToString())); } public static void PrintStack() @@ -93,25 +93,25 @@ namespace Godot public static void PrintErr(params object[] what) { - godot_icall_GD_printerr(what); + godot_icall_GD_printerr(Array.ConvertAll(what, x => x.ToString())); } public static void PrintRaw(params object[] what) { - godot_icall_GD_printraw(what); + godot_icall_GD_printraw(Array.ConvertAll(what, x => x.ToString())); } public static void PrintS(params object[] what) { - godot_icall_GD_prints(what); + godot_icall_GD_prints(Array.ConvertAll(what, x => x.ToString())); } public static void PrintT(params object[] what) { - godot_icall_GD_printt(what); + godot_icall_GD_printt(Array.ConvertAll(what, x => x.ToString())); } - public static double Randf() + public static float Randf() { return godot_icall_GD_randf(); } @@ -224,7 +224,7 @@ namespace Godot internal extern static void godot_icall_GD_printt(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static double godot_icall_GD_randf(); + internal extern static float godot_icall_GD_randf(); [MethodImpl(MethodImplOptions.InternalCall)] internal extern static uint godot_icall_GD_randi(); @@ -232,6 +232,7 @@ namespace Godot [MethodImpl(MethodImplOptions.InternalCall)] internal extern static void godot_icall_GD_randomize(); + [MethodImpl(MethodImplOptions.InternalCall)] internal extern static double godot_icall_GD_rand_range(double from, double to); diff --git a/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs b/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs new file mode 100644 index 0000000000..c3fa2f3e82 --- /dev/null +++ b/modules/mono/glue/Managed/Files/Interfaces/ISerializationListener.cs @@ -0,0 +1,8 @@ +namespace Godot +{ + public interface ISerializationListener + { + void OnBeforeSerialize(); + void OnAfterDeserialize(); + } +} diff --git a/modules/mono/glue/Managed/Files/MarshalUtils.cs b/modules/mono/glue/Managed/Files/MarshalUtils.cs index 7e72b0edb5..a1d63a62ef 100644 --- a/modules/mono/glue/Managed/Files/MarshalUtils.cs +++ b/modules/mono/glue/Managed/Files/MarshalUtils.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; namespace Godot { @@ -8,29 +9,151 @@ namespace Godot static class MarshalUtils { + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="Godot.Collections.Array{T}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="System.InvalidOperationException"> + /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false. + /// </exception> static bool TypeIsGenericArray(Type type) { return type.GetGenericTypeDefinition() == typeof(Godot.Collections.Array<>); } + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="Godot.Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="System.InvalidOperationException"> + /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false. + /// </exception> static bool TypeIsGenericDictionary(Type type) { return type.GetGenericTypeDefinition() == typeof(Godot.Collections.Dictionary<,>); } - static void ArrayGetElementType(Type type, out Type elementType) + static void ArrayGetElementType(Type arrayType, out Type elementType) { - elementType = type.GetGenericArguments()[0]; + elementType = arrayType.GetGenericArguments()[0]; } - static void DictionaryGetKeyValueTypes(Type type, out Type keyType, out Type valueType) + static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) { - var genericArgs = type.GetGenericArguments(); - + var genericArgs = dictionaryType.GetGenericArguments(); keyType = genericArgs[0]; valueType = genericArgs[1]; } + static bool GenericIEnumerableIsAssignableFromType(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + return true; + + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + return true; + } + + Type baseType = type.BaseType; + + if (baseType == null) + return false; + + return GenericIEnumerableIsAssignableFromType(baseType); + } + + static bool GenericIDictionaryIsAssignableFromType(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + return true; + + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + return true; + } + + Type baseType = type.BaseType; + + if (baseType == null) + return false; + + return GenericIDictionaryIsAssignableFromType(baseType); + } + + static bool GenericIEnumerableIsAssignableFromType(Type type, out Type elementType) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + elementType = type.GetGenericArguments()[0]; + return true; + } + + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + elementType = interfaceType.GetGenericArguments()[0]; + return true; + } + } + + Type baseType = type.BaseType; + + if (baseType == null) + { + elementType = null; + return false; + } + + return GenericIEnumerableIsAssignableFromType(baseType, out elementType); + } + + static bool GenericIDictionaryIsAssignableFromType(Type type, out Type keyType, out Type valueType) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + { + var genericArgs = type.GetGenericArguments(); + keyType = genericArgs[0]; + valueType = genericArgs[1]; + return true; + } + + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + { + var genericArgs = interfaceType.GetGenericArguments(); + keyType = genericArgs[0]; + valueType = genericArgs[1]; + return true; + } + } + + Type baseType = type.BaseType; + + if (baseType == null) + { + keyType = null; + valueType = null; + return false; + } + + return GenericIDictionaryIsAssignableFromType(baseType, out keyType, out valueType); + } + + static Type MakeGenericArrayType(Type elemType) + { + return typeof(Godot.Collections.Array<>).MakeGenericType(elemType); + } + + static Type MakeGenericDictionaryType(Type keyType, Type valueType) + { + return typeof(Godot.Collections.Dictionary<,>).MakeGenericType(keyType, valueType); + } + // TODO Add support for IEnumerable<T> and IDictionary<TKey, TValue> // TODO: EnumerableToArray and IDictionaryToDictionary can be optimized @@ -64,5 +187,26 @@ namespace Godot Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, entry.Key, entry.Value); } } + + internal static void GenericIDictionaryToDictionary(object dictionary, IntPtr godotDictionaryPtr) + { +#if DEBUG + if (!GenericIDictionaryIsAssignableFromType(dictionary.GetType())) + throw new InvalidOperationException("The type does not implement IDictionary<,>"); +#endif + + // TODO: Can we optimize this? + + var keys = ((IEnumerable)dictionary.GetType().GetProperty("Keys").GetValue(dictionary)).GetEnumerator(); + var values = ((IEnumerable)dictionary.GetType().GetProperty("Values").GetValue(dictionary)).GetEnumerator(); + + while (keys.MoveNext() && values.MoveNext()) + { + object key = keys.Current; + object value = values.Current; + + Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, key, value); + } + } } } diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs index a064278237..2d8c63fe7f 100644 --- a/modules/mono/glue/Managed/Files/Mathf.cs +++ b/modules/mono/glue/Managed/Files/Mathf.cs @@ -44,9 +44,9 @@ namespace Godot return (real_t)Math.Atan(s); } - public static real_t Atan2(real_t x, real_t y) + public static real_t Atan2(real_t y, real_t x) { - return (real_t)Math.Atan2(x, y); + return (real_t)Math.Atan2(y, x); } public static Vector2 Cartesian2Polar(real_t x, real_t y) @@ -79,14 +79,27 @@ namespace Godot return (real_t)Math.Cosh(s); } - public static int Decimals(real_t step) - { - return Decimals((decimal)step); - } - - public static int Decimals(decimal step) - { - return BitConverter.GetBytes(decimal.GetBits(step)[3])[2]; + public static int StepDecimals(real_t step) + { + double[] sd = new double[] { + 0.9999, + 0.09999, + 0.009999, + 0.0009999, + 0.00009999, + 0.000009999, + 0.0000009999, + 0.00000009999, + 0.000000009999, + }; + double abs = Mathf.Abs(step); + double decs = abs - (int)abs; // Strip away integer part + for (int i = 0; i < sd.Length; i++) { + if (decs >= sd[i]) { + return i; + } + } + return 0; } public static real_t Deg2Rad(real_t deg) @@ -143,6 +156,15 @@ namespace Godot return (weight - from) / (to - from); } + public static bool IsEqualApprox(real_t a, real_t b) + { + real_t tolerance = Epsilon * Abs(a); + if (tolerance < Epsilon) { + tolerance = Epsilon; + } + return Abs(a - b) < tolerance; + } + public static bool IsInf(real_t s) { return real_t.IsInfinity(s); @@ -153,6 +175,11 @@ namespace Godot return real_t.IsNaN(s); } + public static bool IsZeroApprox(real_t s) + { + return Abs(s) < Epsilon; + } + public static real_t Lerp(real_t from, real_t to, real_t weight) { return from + (to - from) * weight; @@ -183,6 +210,11 @@ namespace Godot return a < b ? a : b; } + public static real_t MoveToward(real_t from, real_t to, real_t delta) + { + return Abs(to - from) <= delta ? to : from + Sign(to - from) * delta; + } + public static int NearestPo2(int value) { value--; diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs index 414762f7b1..b96f01bc2e 100644 --- a/modules/mono/glue/Managed/Files/MathfEx.cs +++ b/modules/mono/glue/Managed/Files/MathfEx.cs @@ -21,6 +21,16 @@ namespace Godot public const real_t Epsilon = 1e-06f; #endif + public static int DecimalCount(real_t s) + { + return DecimalCount((decimal)s); + } + + public static int DecimalCount(decimal s) + { + return BitConverter.GetBytes(decimal.GetBits(s)[3])[2]; + } + public static int CeilToInt(real_t s) { return (int)Math.Ceiling(s); @@ -36,9 +46,9 @@ namespace Godot return (int)Math.Round(s); } - public static bool IsEqualApprox(real_t a, real_t b, real_t ratio = Mathf.Epsilon) + public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance) { - return Abs(a - b) < ratio; + return Abs(a - b) < tolerance; } } }
\ No newline at end of file diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs index f11cd490a9..e16d4315be 100644 --- a/modules/mono/glue/Managed/Files/Plane.cs +++ b/modules/mono/glue/Managed/Files/Plane.cs @@ -200,7 +200,7 @@ namespace Godot public bool Equals(Plane other) { - return _normal == other._normal && D == other.D; + return _normal == other._normal && Mathf.IsEqualApprox(D, other.D); } public override int GetHashCode() diff --git a/modules/mono/glue/Managed/Files/Quat.cs b/modules/mono/glue/Managed/Files/Quat.cs index d0c15146a5..0d4349084a 100644 --- a/modules/mono/glue/Managed/Files/Quat.cs +++ b/modules/mono/glue/Managed/Files/Quat.cs @@ -358,7 +358,7 @@ namespace Godot public bool Equals(Quat other) { - return x == other.x && y == other.y && z == other.z && w == other.w; + return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w); } public override int GetHashCode() diff --git a/modules/mono/glue/Managed/Files/StringExtensions.cs b/modules/mono/glue/Managed/Files/StringExtensions.cs index c194facd0b..b43034fbb5 100644 --- a/modules/mono/glue/Managed/Files/StringExtensions.cs +++ b/modules/mono/glue/Managed/Files/StringExtensions.cs @@ -299,14 +299,14 @@ namespace Godot if (basepos != -1) { var end = basepos + 3; - rs = instance.Substring(end, instance.Length); + rs = instance.Substring(end); @base = instance.Substring(0, end); } else { if (instance.BeginsWith("/")) { - rs = instance.Substring(1, instance.Length); + rs = instance.Substring(1); @base = "/"; } else @@ -333,7 +333,7 @@ namespace Godot if (sep == -1) return instance; - return instance.Substring(sep + 1, instance.Length); + return instance.Substring(sep + 1); } // <summary> @@ -911,7 +911,8 @@ namespace Godot // </summary> public static string Substr(this string instance, int from, int len) { - return instance.Substring(from, len); + int max = instance.Length - from; + return instance.Substring(from, len > max ? max : len); } // <summary> diff --git a/modules/mono/glue/Managed/Files/Transform2D.cs b/modules/mono/glue/Managed/Files/Transform2D.cs index f7bb41d523..33ff286769 100644 --- a/modules/mono/glue/Managed/Files/Transform2D.cs +++ b/modules/mono/glue/Managed/Files/Transform2D.cs @@ -298,6 +298,7 @@ namespace Godot origin = originPos; } + // Arguments are named such that xy is equal to calling x.y public Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) { x = new Vector2(xx, xy); diff --git a/modules/mono/glue/Managed/Files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs index 908162ec45..a7f26283a7 100644 --- a/modules/mono/glue/Managed/Files/Vector2.cs +++ b/modules/mono/glue/Managed/Files/Vector2.cs @@ -52,11 +52,15 @@ namespace Godot internal void Normalize() { - real_t length = x * x + y * y; + real_t lengthsq = LengthSquared(); - if (length != 0f) + if (lengthsq == 0) { - length = Mathf.Sqrt(length); + x = y = 0f; + } + else + { + real_t length = Mathf.Sqrt(lengthsq); x /= length; y /= length; } @@ -182,11 +186,19 @@ namespace Godot return res; } + public Vector2 MoveToward(Vector2 to, real_t delta) + { + var v = this; + var vd = to - v; + var len = vd.Length(); + return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; + } + public Vector2 Normalized() { - var result = this; - result.Normalize(); - return result; + var v = this; + v.Normalize(); + return v; } public Vector2 Project(Vector2 onNormal) @@ -343,7 +355,7 @@ namespace Godot public static bool operator <(Vector2 left, Vector2 right) { - if (left.x.Equals(right.x)) + if (Mathf.IsEqualApprox(left.x, right.x)) { return left.y < right.y; } @@ -353,7 +365,7 @@ namespace Godot public static bool operator >(Vector2 left, Vector2 right) { - if (left.x.Equals(right.x)) + if (Mathf.IsEqualApprox(left.x, right.x)) { return left.y > right.y; } @@ -363,7 +375,7 @@ namespace Godot public static bool operator <=(Vector2 left, Vector2 right) { - if (left.x.Equals(right.x)) + if (Mathf.IsEqualApprox(left.x, right.x)) { return left.y <= right.y; } @@ -373,7 +385,7 @@ namespace Godot public static bool operator >=(Vector2 left, Vector2 right) { - if (left.x.Equals(right.x)) + if (Mathf.IsEqualApprox(left.x, right.x)) { return left.y >= right.y; } @@ -393,7 +405,7 @@ namespace Godot public bool Equals(Vector2 other) { - return x == other.x && y == other.y; + return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y); } public override int GetHashCode() diff --git a/modules/mono/glue/Managed/Files/Vector3.cs b/modules/mono/glue/Managed/Files/Vector3.cs index 0c96d346a9..16803ae55c 100644 --- a/modules/mono/glue/Managed/Files/Vector3.cs +++ b/modules/mono/glue/Managed/Files/Vector3.cs @@ -65,14 +65,15 @@ namespace Godot internal void Normalize() { - real_t length = Length(); + real_t lengthsq = LengthSquared(); - if (length == 0f) + if (lengthsq == 0) { x = y = z = 0f; } else { + real_t length = Mathf.Sqrt(lengthsq); x /= length; y /= length; z /= length; @@ -189,6 +190,14 @@ namespace Godot ); } + public Vector3 MoveToward(Vector3 to, real_t delta) + { + var v = this; + var vd = to - v; + var len = vd.Length(); + return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; + } + public Axis MaxAxis() { return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X); @@ -397,9 +406,9 @@ namespace Godot public static bool operator <(Vector3 left, Vector3 right) { - if (left.x == right.x) + if (Mathf.IsEqualApprox(left.x, right.x)) { - if (left.y == right.y) + if (Mathf.IsEqualApprox(left.y, right.y)) return left.z < right.z; return left.y < right.y; } @@ -409,9 +418,9 @@ namespace Godot public static bool operator >(Vector3 left, Vector3 right) { - if (left.x == right.x) + if (Mathf.IsEqualApprox(left.x, right.x)) { - if (left.y == right.y) + if (Mathf.IsEqualApprox(left.y, right.y)) return left.z > right.z; return left.y > right.y; } @@ -421,9 +430,9 @@ namespace Godot public static bool operator <=(Vector3 left, Vector3 right) { - if (left.x == right.x) + if (Mathf.IsEqualApprox(left.x, right.x)) { - if (left.y == right.y) + if (Mathf.IsEqualApprox(left.y, right.y)) return left.z <= right.z; return left.y < right.y; } @@ -433,9 +442,9 @@ namespace Godot public static bool operator >=(Vector3 left, Vector3 right) { - if (left.x == right.x) + if (Mathf.IsEqualApprox(left.x, right.x)) { - if (left.y == right.y) + if (Mathf.IsEqualApprox(left.y, right.y)) return left.z >= right.z; return left.y > right.y; } @@ -455,7 +464,7 @@ namespace Godot public bool Equals(Vector3 other) { - return x == other.x && y == other.y && z == other.z; + return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z); } public override int GetHashCode() diff --git a/modules/mono/glue/Managed/IgnoredFiles/Node.cs b/modules/mono/glue/Managed/IgnoredFiles/Node.cs index 99ba0f827a..cff61b1e0b 100644 --- a/modules/mono/glue/Managed/IgnoredFiles/Node.cs +++ b/modules/mono/glue/Managed/IgnoredFiles/Node.cs @@ -15,9 +15,10 @@ namespace Godot throw new NotImplementedException(); } - public Node GetOwner() + public Node Owner { - throw new NotImplementedException(); + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); } public Node GetParent() diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 7385014a53..6d85f55b97 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -166,7 +166,7 @@ MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) { int i = 0; for (List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get().name); - mono_array_set(result, MonoString *, i, boxed); + mono_array_setref(result, i, boxed); i++; } @@ -219,7 +219,18 @@ MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString * } MonoString *godot_icall_Object_ToString(Object *p_ptr) { - return GDMonoMarshal::mono_string_from_godot(Variant(p_ptr).operator String()); +#ifdef DEBUG_ENABLED + // Cannot happen in C#; would get an ObjectDisposedException instead. + CRASH_COND(p_ptr == NULL); + + if (ScriptDebugger::get_singleton() && !Object::cast_to<Reference>(p_ptr)) { // Only if debugging! + // Cannot happen either in C#; the handle is nullified when the object is destroyed + CRASH_COND(!ObjectDB::instance_validate(p_ptr)); + } +#endif + + String result = "[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]"; + return GDMonoMarshal::mono_string_from_godot(result); } void godot_register_object_icalls() { diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index 4aef5684fd..e67c8b9ad9 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -46,7 +46,7 @@ void godot_icall_Array_Dtor(Array *ptr) { } MonoObject *godot_icall_Array_At(Array *ptr, int index) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return NULL; } @@ -54,7 +54,7 @@ MonoObject *godot_icall_Array_At(Array *ptr, int index) { } MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return NULL; } @@ -62,7 +62,7 @@ MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_en } void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; } @@ -124,7 +124,7 @@ MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) { } void godot_icall_Array_RemoveAt(Array *ptr, int index) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; } @@ -162,7 +162,7 @@ MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) { #ifdef DEBUG_ENABLED CRASH_COND(!exc); #endif - GDMonoUtils::runtime_object_init(exc); + GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException)); GDMonoUtils::set_pending_exception((MonoException *)exc); return NULL; } @@ -176,7 +176,7 @@ MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject #ifdef DEBUG_ENABLED CRASH_COND(!exc); #endif - GDMonoUtils::runtime_object_init(exc); + GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException)); GDMonoUtils::set_pending_exception((MonoException *)exc); return NULL; } diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index d756131ac9..7c30092855 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -115,7 +115,7 @@ void godot_icall_GD_printt(MonoArray *p_what) { print_line(str); } -double godot_icall_GD_randf() { +float godot_icall_GD_randf() { return Math::randf(); } diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h index 910979aae3..d4e20e2887 100644 --- a/modules/mono/glue/gd_glue.h +++ b/modules/mono/glue/gd_glue.h @@ -53,7 +53,7 @@ void godot_icall_GD_prints(MonoArray *p_what); void godot_icall_GD_printt(MonoArray *p_what); -double godot_icall_GD_randf(); +float godot_icall_GD_randf(); uint32_t godot_icall_GD_randi(); diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp index a5c72160d7..e9373fb486 100644 --- a/modules/mono/glue/string_glue.cpp +++ b/modules/mono/glue/string_glue.cpp @@ -68,12 +68,12 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) { } void godot_register_string_icalls() { - mono_add_internal_call("Godot.String::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer); - mono_add_internal_call("Godot.String::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text); - mono_add_internal_call("Godot.String::godot_icall_String_rfind", (void *)godot_icall_String_rfind); - mono_add_internal_call("Godot.String::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn); - mono_add_internal_call("Godot.String::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer); - mono_add_internal_call("Godot.String::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", (void *)godot_icall_String_rfind); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer); + mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index 0d3b96d789..4ad4088514 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -39,7 +39,8 @@ #define API_SOLUTION_NAME "GodotSharp" #define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" -#define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools" +#define TOOLS_ASSEMBLY_NAME "GodotTools" +#define TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME "GodotTools.ProjectEditor" #define BINDINGS_CLASS_NATIVECALLS "NativeCalls" #define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 09a1fc6fbc..4b2525c692 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -39,6 +39,10 @@ #include "editor/editor_settings.h" #endif +#ifdef __ANDROID__ +#include "utils/android_utils.h" +#endif + namespace GodotSharpDirs { String _get_expected_build_config() { @@ -55,6 +59,20 @@ String _get_expected_build_config() { #endif } +String _get_expected_api_build_config() { +#ifdef TOOLS_ENABLED + return "Debug"; +#else + +#ifdef DEBUG_ENABLED + return "Debug"; +#else + return "Release"; +#endif + +#endif +} + String _get_mono_user_dir() { #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { @@ -84,6 +102,7 @@ class _GodotSharpDirs { public: String res_data_dir; String res_metadata_dir; + String res_assemblies_base_dir; String res_assemblies_dir; String res_config_dir; String res_temp_dir; @@ -114,7 +133,8 @@ private: _GodotSharpDirs() { res_data_dir = "res://.mono"; res_metadata_dir = res_data_dir.plus_file("metadata"); - res_assemblies_dir = res_data_dir.plus_file("assemblies"); + res_assemblies_base_dir = res_data_dir.plus_file("assemblies"); + res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config()); res_config_dir = res_data_dir.plus_file("etc").plus_file("mono"); // TODO use paths from csproj @@ -129,15 +149,16 @@ private: mono_solutions_dir = mono_user_dir.plus_file("solutions"); build_logs_dir = mono_user_dir.plus_file("build_logs"); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } String base_path = ProjectSettings::get_singleton()->globalize_path("res://"); - sln_filepath = base_path.plus_file(name + ".sln"); - csproj_filepath = base_path.plus_file(name + ".csproj"); + sln_filepath = base_path.plus_file(appname_safe + ".sln"); + csproj_filepath = base_path.plus_file(appname_safe + ".csproj"); #endif String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); @@ -150,7 +171,12 @@ private: String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); + +#if __ANDROID__ + data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir(); +#else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); +#endif #ifdef WINDOWS_ENABLED data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); @@ -173,15 +199,21 @@ private: #else - String appname = OS::get_singleton()->get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name")); - String data_dir_root = exe_dir.plus_file("data_" + appname); + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + String data_dir_root = exe_dir.plus_file("data_" + appname_safe); if (!DirAccess::exists(data_dir_root)) { data_dir_root = exe_dir.plus_file("data_Godot"); } String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); + +#if __ANDROID__ + data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir(); +#else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); +#endif #ifdef WINDOWS_ENABLED data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); @@ -215,6 +247,10 @@ String get_res_metadata_dir() { return _GodotSharpDirs::get_singleton().res_metadata_dir; } +String get_res_assemblies_base_dir() { + return _GodotSharpDirs::get_singleton().res_assemblies_base_dir; +} + String get_res_assemblies_dir() { return _GodotSharpDirs::get_singleton().res_assemblies_dir; } diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h index 556df959e2..ff51888d1c 100644 --- a/modules/mono/godotsharp_dirs.h +++ b/modules/mono/godotsharp_dirs.h @@ -37,6 +37,7 @@ namespace GodotSharpDirs { String get_res_data_dir(); String get_res_metadata_dir(); +String get_res_assemblies_base_dir(); String get_res_assemblies_dir(); String get_res_config_dir(); String get_res_temp_dir(); diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h index 63b61aff18..60a1eed212 100644 --- a/modules/mono/mono_gc_handle.h +++ b/modules/mono/mono_gc_handle.h @@ -37,7 +37,7 @@ class MonoGCHandle : public Reference { - GDCLASS(MonoGCHandle, Reference) + GDCLASS(MonoGCHandle, Reference); bool released; bool weak; diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index bfb6c13224..06fbae019c 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -52,10 +52,13 @@ #include "gd_mono_utils.h" #ifdef TOOLS_ENABLED -#include "../editor/godotsharp_editor.h" #include "main/main.h" #endif +#ifdef ANDROID_ENABLED +#include "android_mono_config.gen.h" +#endif + #define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. " \ "This error is expected if you just upgraded to a newer Godot version. " \ "Building the project will update the assembly to the correct version." @@ -95,7 +98,7 @@ void gdmono_profiler_init() { #ifdef DEBUG_ENABLED -static bool _wait_for_debugger_msecs(uint32_t p_msecs) { +bool _wait_for_debugger_msecs(uint32_t p_msecs) { do { if (mono_is_debugger_attached()) @@ -125,16 +128,17 @@ void gdmono_debug_init() { bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); + CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); + #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() || ProjectSettings::get_singleton()->get_resource_path().empty() || Main::is_project_manager()) { - return; + if (da_args.size() == 0) + return; } #endif - CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); - if (da_args.length() == 0) { da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) + ",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n")) @@ -203,6 +207,10 @@ void GDMono::initialize() { print_verbose("Mono: Initializing module..."); + char *runtime_build_info = mono_get_runtime_build_info(); + print_verbose("Mono JIT compiler version " + String(runtime_build_info)); + mono_free(runtime_build_info); + #ifdef DEBUG_METHODS_ENABLED _initialize_and_check_api_hashes(); #endif @@ -233,9 +241,9 @@ void GDMono::initialize() { locations.push_back("/usr/local/var/homebrew/linked/mono/"); for (int i = 0; i < locations.size(); i++) { - String hint_assembly_rootdir = path_join(locations[i], "lib"); - String hint_mscorlib_path = path_join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll"); - String hint_config_dir = path_join(locations[i], "etc"); + String hint_assembly_rootdir = path::join(locations[i], "lib"); + String hint_mscorlib_path = path::join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll"); + String hint_config_dir = path::join(locations[i], "etc"); if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) { assembly_rootdir = hint_assembly_rootdir; @@ -251,17 +259,19 @@ void GDMono::initialize() { String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir(); #ifdef TOOLS_ENABLED - if (DirAccess::exists(bundled_assembly_rootdir) && DirAccess::exists(bundled_config_dir)) { + if (DirAccess::exists(bundled_assembly_rootdir)) { assembly_rootdir = bundled_assembly_rootdir; + } + + if (DirAccess::exists(bundled_config_dir)) { config_dir = bundled_config_dir; } #ifdef WINDOWS_ENABLED if (assembly_rootdir.empty() || config_dir.empty()) { + ERR_PRINT("Cannot find Mono in the registry"); // Assertion: if they are not set, then they weren't found in the registry CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0); - - ERR_PRINT("Cannot find Mono in the registry"); } #endif // WINDOWS_ENABLED @@ -285,7 +295,11 @@ void GDMono::initialize() { gdmono_debug_init(); #endif +#ifdef ANDROID_ENABLED + mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data()); +#else mono_config_parse(NULL); +#endif mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL); @@ -329,18 +343,6 @@ void GDMono::initialize() { ERR_EXPLAIN("Mono: Failed to load mscorlib assembly"); ERR_FAIL_COND(!_load_corlib_assembly()); -#ifdef TOOLS_ENABLED - // The tools domain must be loaded here, before the scripts domain. - // Otherwise domain unload on the scripts domain will hang indefinitely. - - ERR_EXPLAIN("Mono: Failed to load tools domain"); - ERR_FAIL_COND(_load_tools_domain() != OK); - - // TODO move to editor init callback, and do it lazily when required before editor init (e.g.: bindings generation) - ERR_EXPLAIN("Mono: Failed to load Editor Tools assembly"); - ERR_FAIL_COND(!_load_editor_tools_assembly()); -#endif - ERR_EXPLAIN("Mono: Failed to load scripts domain"); ERR_FAIL_COND(_load_scripts_domain() != OK); @@ -355,8 +357,15 @@ void GDMono::initialize() { // The following assemblies are not required at initialization #ifdef MONO_GLUE_ENABLED if (_load_api_assemblies()) { - // Everything is fine with the api assemblies, load the project assembly + // Everything is fine with the api assemblies, load the tools and project assemblies + +#if defined(TOOLS_ENABLED) + ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); + ERR_FAIL_COND(!_load_tools_assemblies()); +#endif + _load_project_assembly(); + } else { if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)) #ifdef TOOLS_ENABLED @@ -417,10 +426,6 @@ void GDMono::_register_internal_calls() { #ifdef MONO_GLUE_ENABLED GodotSharpBindings::register_generated_icalls(); #endif - -#ifdef TOOLS_ENABLED - GodotSharpEditor::register_internal_calls(); -#endif } void GDMono::_initialize_and_check_api_hashes() { @@ -559,6 +564,52 @@ bool GDMono::_load_corlib_assembly() { return success; } +#ifdef TOOLS_ENABLED +static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) { + + // Create destination directory if needed + if (!DirAccess::exists(p_dst_dir)) { + DirAccess *da = DirAccess::create_for_path(p_dst_dir); + Error err = da->make_dir_recursive(p_dst_dir); + memdelete(da); + + if (err != OK) { + ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err)); + return false; + } + } + + String assembly_file = p_assembly_name + ".dll"; + String assembly_src = p_src_dir.plus_file(assembly_file); + String assembly_dst = p_dst_dir.plus_file(assembly_file); + + if (!FileAccess::exists(assembly_dst) || + FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) || + GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + String xml_file = p_assembly_name + ".xml"; + if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK) + WARN_PRINTS("Failed to copy " + xml_file); + + String pdb_file = p_assembly_name + ".pdb"; + if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK) + WARN_PRINTS("Failed to copy " + pdb_file); + + Error err = da->copy(assembly_src, assembly_dst); + + if (err != OK) { + ERR_PRINTS("Failed to copy " + assembly_file); + return false; + } + + GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false); + } + + return true; +} +#endif + bool GDMono::_load_core_api_assembly() { if (core_api_assembly) @@ -566,19 +617,31 @@ bool GDMono::_load_core_api_assembly() { #ifdef TOOLS_ENABLED if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { - print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated"); - return false; + String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + String prebuilt_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_CORE); + + if (!FileAccess::exists(prebuilt_dll_path) || + FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) { + print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated"); + return false; + } else { + // Copy the prebuilt Api + String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); + if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, CORE_API_ASSEMBLY_NAME, APIAssembly::API_CORE) || + !copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) { + print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Core API assembly because it was invalidated"); + return false; + } + } } #endif String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - if (!FileAccess::exists(assembly_path)) - return false; - - bool success = load_assembly_from(CORE_API_ASSEMBLY_NAME, - assembly_path, - &core_api_assembly); + bool success = (FileAccess::exists(assembly_path) && + load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly)) || + load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly); if (success) { #ifdef MONO_GLUE_ENABLED @@ -606,18 +669,29 @@ bool GDMono::_load_editor_api_assembly() { return true; if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { - print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated"); - return false; + String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + String prebuilt_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_EDITOR); + + if (!FileAccess::exists(prebuilt_dll_path) || + FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) { + print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated"); + return false; + } else { + // Copy the prebuilt editor Api (no need to copy the core api if we got to this point) + String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); + if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) { + print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Editor API assembly because it was invalidated"); + return false; + } + } } String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - if (!FileAccess::exists(assembly_path)) - return false; - - bool success = load_assembly_from(EDITOR_API_ASSEMBLY_NAME, - assembly_path, - &editor_api_assembly); + bool success = (FileAccess::exists(assembly_path) && + load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly)) || + load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); if (success) { #ifdef MONO_GLUE_ENABLED @@ -633,14 +707,15 @@ bool GDMono::_load_editor_api_assembly() { #endif #ifdef TOOLS_ENABLED -bool GDMono::_load_editor_tools_assembly() { +bool GDMono::_load_tools_assemblies() { - if (editor_tools_assembly) + if (tools_assembly && tools_project_editor_assembly) return true; - _GDMONO_SCOPE_DOMAIN_(tools_domain) + bool success = load_assembly(TOOLS_ASSEMBLY_NAME, &tools_assembly) && + load_assembly(TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME, &tools_project_editor_assembly); - return load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly); + return success; } #endif @@ -649,17 +724,16 @@ bool GDMono::_load_project_assembly() { if (project_assembly) return true; - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } - bool success = load_assembly(name, &project_assembly); + bool success = load_assembly(appname_safe, &project_assembly); if (success) { mono_assembly_set_main(project_assembly->get_assembly()); - - CSharpLanguage::get_singleton()->project_assembly_loaded(); } else { if (OS::get_singleton()->is_stdout_verbose()) print_error("Mono: Failed to load project assembly"); @@ -772,6 +846,14 @@ bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time; } + +String GDMono::get_invalidated_api_assembly_path(APIAssembly::Type p_api_type) { + + return GodotSharpDirs::get_res_assemblies_dir() + .plus_file(p_api_type == APIAssembly::API_CORE ? + CORE_API_ASSEMBLY_NAME ".dll" : + EDITOR_API_ASSEMBLY_NAME ".dll"); +} #endif Error GDMono::_load_scripts_domain() { @@ -809,12 +891,16 @@ Error GDMono::_unload_scripts_domain() { mono_gc_collect(mono_gc_max_generation()); + GDMonoUtils::clear_godot_api_cache(); + _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); core_api_assembly = NULL; project_assembly = NULL; #ifdef TOOLS_ENABLED editor_api_assembly = NULL; + tools_assembly = NULL; + tools_project_editor_assembly = NULL; #endif core_api_assembly_out_of_sync = false; @@ -837,22 +923,6 @@ Error GDMono::_unload_scripts_domain() { return OK; } -#ifdef TOOLS_ENABLED -Error GDMono::_load_tools_domain() { - - ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG); - - print_verbose("Mono: Loading tools domain..."); - - tools_domain = GDMonoUtils::create_domain("GodotEngine.ToolsDomain"); - - ERR_EXPLAIN("Mono: Could not create tools app domain"); - ERR_FAIL_NULL_V(tools_domain, ERR_CANT_CREATE); - - return OK; -} -#endif - #ifdef GD_MONO_HOT_RELOAD Error GDMono::reload_scripts_domain() { @@ -866,7 +936,7 @@ Error GDMono::reload_scripts_domain() { } } - CSharpLanguage::get_singleton()->_uninitialize_script_bindings(); + CSharpLanguage::get_singleton()->_on_scripts_domain_unloaded(); Error err = _load_scripts_domain(); if (err != OK) { @@ -914,6 +984,11 @@ Error GDMono::reload_scripts_domain() { } } +#ifdef TOOLS_ENABLED + ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); + ERR_FAIL_COND_V(!_load_tools_assemblies(), ERR_CANT_OPEN); +#endif + if (!_load_project_assembly()) { return ERR_CANT_OPEN; } @@ -928,6 +1003,7 @@ Error GDMono::reload_scripts_domain() { Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { CRASH_COND(p_domain == NULL); + CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead String domain_name = mono_domain_get_friendly_name(p_domain); @@ -944,18 +1020,12 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { _domain_assemblies_cleanup(mono_domain_get_id(p_domain)); -#ifdef TOOLS_ENABLED - if (p_domain == tools_domain) { - editor_tools_assembly = NULL; - } -#endif - MonoException *exc = NULL; mono_domain_try_unload(p_domain, (MonoObject **)&exc); if (exc) { ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`"); - GDMonoUtils::debug_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); return FAILED; } @@ -986,6 +1056,22 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { return NULL; } +GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) { + + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; + + const String *k = NULL; + while ((k = domain_assemblies.next(k))) { + GDMonoAssembly *assembly = domain_assemblies.get(*k); + GDMonoClass *klass = assembly->get_class(p_namespace, p_name); + if (klass) + return klass; + } + + return NULL; +} + void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) { HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id]; @@ -1026,9 +1112,6 @@ GDMono::GDMono() { root_domain = NULL; scripts_domain = NULL; -#ifdef TOOLS_ENABLED - tools_domain = NULL; -#endif core_api_assembly_out_of_sync = false; #ifdef TOOLS_ENABLED @@ -1040,7 +1123,8 @@ GDMono::GDMono() { project_assembly = NULL; #ifdef TOOLS_ENABLED editor_api_assembly = NULL; - editor_tools_assembly = NULL; + tools_assembly = NULL; + tools_project_editor_assembly = NULL; #endif api_core_hash = 0; @@ -1052,16 +1136,6 @@ GDMono::GDMono() { GDMono::~GDMono() { if (is_runtime_initialized()) { - -#ifdef TOOLS_ENABLED - if (tools_domain) { - Error err = finalize_and_unload_domain(tools_domain); - if (err != OK) { - ERR_PRINT("Mono: Failed to unload tools domain"); - } - } -#endif - if (scripts_domain) { Error err = _unload_scripts_domain(); if (err != OK) { @@ -1080,8 +1154,6 @@ GDMono::~GDMono() { } assemblies.clear(); - GDMonoUtils::clear_cache(); - print_verbose("Mono: Runtime cleanup..."); mono_jit_cleanup(root_domain); @@ -1118,14 +1190,14 @@ int32_t _GodotSharp::get_domain_id() { int32_t _GodotSharp::get_scripts_domain_id() { - MonoDomain *domain = SCRIPTS_DOMAIN; + MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain(); CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method return mono_domain_get_id(domain); } bool _GodotSharp::is_scripts_domain_loaded() { - return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL; + return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != NULL; } bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { @@ -1147,7 +1219,7 @@ bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { if (!p_domain) return true; - if (p_domain == SCRIPTS_DOMAIN && GDMono::get_singleton()->is_finalizing_scripts_domain()) + if (p_domain == GDMono::get_singleton()->get_scripts_domain() && GDMono::get_singleton()->is_finalizing_scripts_domain()) return true; return mono_domain_is_unloading(p_domain); } @@ -1162,6 +1234,12 @@ bool _GodotSharp::is_runtime_initialized() { return GDMono::get_singleton()->is_runtime_initialized(); } +void _GodotSharp::_reload_assemblies(bool p_soft_reload) { +#ifdef GD_MONO_HOT_RELOAD + CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload); +#endif +} + void _GodotSharp::_bind_methods() { ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread); @@ -1174,6 +1252,7 @@ void _GodotSharp::_bind_methods() { ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down); ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized); + ClassDB::bind_method(D_METHOD("_reload_assemblies"), &_GodotSharp::_reload_assemblies); } _GodotSharp::_GodotSharp() { diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 216c96a612..a926bf4126 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -78,11 +78,6 @@ struct Version { String to_string(Type p_type); } // namespace APIAssembly -#define SCRIPTS_DOMAIN GDMono::get_singleton()->get_scripts_domain() -#ifdef TOOLS_ENABLED -#define TOOLS_DOMAIN GDMono::get_singleton()->get_tools_domain() -#endif - class GDMono { bool runtime_initialized; @@ -90,9 +85,6 @@ class GDMono { MonoDomain *root_domain; MonoDomain *scripts_domain; -#ifdef TOOLS_ENABLED - MonoDomain *tools_domain; -#endif bool core_api_assembly_out_of_sync; #ifdef TOOLS_ENABLED @@ -104,7 +96,8 @@ class GDMono { GDMonoAssembly *project_assembly; #ifdef TOOLS_ENABLED GDMonoAssembly *editor_api_assembly; - GDMonoAssembly *editor_tools_assembly; + GDMonoAssembly *tools_assembly; + GDMonoAssembly *tools_project_editor_assembly; #endif HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies; @@ -115,7 +108,7 @@ class GDMono { bool _load_core_api_assembly(); #ifdef TOOLS_ENABLED bool _load_editor_api_assembly(); - bool _load_editor_tools_assembly(); + bool _load_tools_assemblies(); #endif bool _load_project_assembly(); @@ -132,10 +125,6 @@ class GDMono { Error _load_scripts_domain(); Error _unload_scripts_domain(); -#ifdef TOOLS_ENABLED - Error _load_tools_domain(); -#endif - uint64_t api_core_hash; #ifdef TOOLS_ENABLED uint64_t api_editor_hash; @@ -170,6 +159,7 @@ public: #ifdef TOOLS_ENABLED void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated); bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type); + String get_invalidated_api_assembly_path(APIAssembly::Type p_api_type); #endif static GDMono *get_singleton() { return singleton; } @@ -185,16 +175,14 @@ public: _FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; } _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } -#ifdef TOOLS_ENABLED - _FORCE_INLINE_ MonoDomain *get_tools_domain() { return tools_domain; } -#endif _FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; } _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; } _FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; } #ifdef TOOLS_ENABLED _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_editor_tools_assembly() const { return editor_tools_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; } #endif #if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) @@ -202,6 +190,7 @@ public: #endif GDMonoClass *get_class(MonoClass *p_raw_class); + GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); #ifdef GD_MONO_HOT_RELOAD Error reload_scripts_domain(); @@ -267,7 +256,7 @@ public: (void)__gdmono__scope__exit__domain__unload__; class _GodotSharp : public Object { - GDCLASS(_GodotSharp, Object) + GDCLASS(_GodotSharp, Object); friend class GDMono; @@ -276,6 +265,8 @@ class _GodotSharp : public Object { List<NodePath *> np_delete_queue; List<RID *> rid_delete_queue; + void _reload_assemblies(bool p_soft_reload); + protected: static _GodotSharp *singleton; static void _bind_methods(); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 8fec28b186..8e63ef3563 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -46,11 +46,31 @@ bool GDMonoAssembly::in_preload = false; Vector<String> GDMonoAssembly::search_dirs; -void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config) { +static String _get_expected_api_build_config() { +#ifdef TOOLS_ENABLED + return "Debug"; +#else + +#ifdef DEBUG_ENABLED + return "Debug"; +#else + return "Release"; +#endif + +#endif +} + +void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) { + + String framework_dir; - const char *rootdir = mono_assembly_getrootdir(); - if (rootdir) { - String framework_dir = String::utf8(rootdir).plus_file("mono").plus_file("4.5"); + if (!p_custom_bcl_dir.empty()) { + framework_dir = p_custom_bcl_dir; + } else if (mono_assembly_getrootdir()) { + framework_dir = String::utf8(mono_assembly_getrootdir()).plus_file("mono").plus_file("4.5"); + } + + if (!framework_dir.empty()) { r_search_dirs.push_back(framework_dir); r_search_dirs.push_back(framework_dir.plus_file("Facades")); } @@ -61,11 +81,19 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); } + String api_config = p_custom_config.empty() ? _get_expected_api_build_config() : + (p_custom_config == "Release" ? "Release" : "Debug"); + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config)); + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); r_search_dirs.push_back(OS::get_singleton()->get_resource_dir()); r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir()); + #ifdef TOOLS_ENABLED r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir()); + + // For GodotTools to find the api assemblies + r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug")); #endif } @@ -264,7 +292,18 @@ Error GDMonoAssembly::load(bool p_refonly) { Vector<uint8_t> data = FileAccess::get_file_as_array(path); ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ); - String image_filename = ProjectSettings::get_singleton()->globalize_path(path); + String image_filename; + +#ifdef ANDROID_ENABLED + if (path.begins_with("res://")) { + image_filename = path.substr(6, path.length()); + } else { + image_filename = ProjectSettings::get_singleton()->globalize_path(path); + } +#else + // FIXME: globalize_path does not work on exported games + image_filename = ProjectSettings::get_singleton()->globalize_path(path); +#endif MonoImageOpenStatus status = MONO_IMAGE_OK; diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 32432af37d..39749dfc1d 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -122,7 +122,7 @@ public: GDMonoClass *get_object_derived_class(const StringName &p_class); - static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String()); + static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 4342f46109..1c10d3c8eb 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -41,7 +41,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) { MonoException *exc = NULL; MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return GDMonoMarshal::mono_string_to_godot(str); } @@ -74,16 +74,13 @@ bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { } GDMonoClass *GDMonoClass::get_parent_class() { + MonoClass *parent_mono_class = mono_class_get_parent(mono_class); + return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : NULL; +} - if (assembly) { - MonoClass *parent_mono_class = mono_class_get_parent(mono_class); - - if (parent_mono_class) { - return GDMono::get_singleton()->get_class(parent_mono_class); - } - } - - return NULL; +GDMonoClass *GDMonoClass::get_nesting_class() { + MonoClass *nesting_type = mono_class_get_nesting_type(mono_class); + return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : NULL; } #ifdef TOOLS_ENABLED diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h index 249422b844..40e1574927 100644 --- a/modules/mono/mono_gd/gd_mono_class.h +++ b/modules/mono/mono_gd/gd_mono_class.h @@ -121,6 +121,7 @@ public: _FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; } GDMonoClass *get_parent_class(); + GDMonoClass *get_nesting_class(); #ifdef TOOLS_ENABLED Vector<MonoClassField *> get_enum_fields(); diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 9779797d1a..3999658f93 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -313,18 +313,44 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } + // The order in which we check the following interfaces is very important (dictionaries and generics first) + + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); + + MonoReflectionType *key_reftype, *value_reftype; + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), + GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype)); + mono_field_set_value(p_object, mono_field, managed); + break; + } + if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary)); mono_field_set_value(p_object, mono_field, managed); break; } - if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); + MonoReflectionType *elem_reftype; + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), + GDMonoUtils::Marshal::make_generic_array_type(elem_reftype)); mono_field_set_value(p_object, mono_field, managed); break; } + if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { + if (GDMonoUtils::tools_godot_api_check()) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); + mono_field_set_value(p_object, mono_field, managed); + break; + } else { + MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array()); + mono_field_set_value(p_object, mono_field, managed); + break; + } + } + ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name()); ERR_FAIL(); } break; @@ -430,28 +456,26 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); - - MonoException *exc = NULL; + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type()); - GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary); - MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - - if (is_dict) { + if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class); mono_field_set_value(p_object, mono_field, managed); break; } - exc = NULL; + if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class); + mono_field_set_value(p_object, mono_field, managed); + break; + } - GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray); - MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + // The order in which we check the following interfaces is very important (dictionaries and generics first) - if (is_array) { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class); + MonoReflectionType *key_reftype, *value_reftype; + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), + GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype)); mono_field_set_value(p_object, mono_field, managed); break; } @@ -462,11 +486,25 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } - if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); + MonoReflectionType *elem_reftype; + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), + GDMonoUtils::Marshal::make_generic_array_type(elem_reftype)); mono_field_set_value(p_object, mono_field, managed); break; } + + if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { + if (GDMonoUtils::tools_godot_api_check()) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); + mono_field_set_value(p_object, mono_field, managed); + break; + } else { + MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array()); + mono_field_set_value(p_object, mono_field, managed); + break; + } + } } break; default: { diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h index e348583370..a7727ddf34 100644 --- a/modules/mono/mono_gd/gd_mono_field.h +++ b/modules/mono/mono_gd/gd_mono_field.h @@ -47,9 +47,11 @@ class GDMonoField : public IMonoClassMember { MonoCustomAttrInfo *attributes; public: - virtual MemberType get_member_type() GD_FINAL { return MEMBER_TYPE_FIELD; } + virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; } - virtual StringName get_name() GD_FINAL { return name; } + virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_FIELD; } + + virtual StringName get_name() const GD_FINAL { return name; } virtual bool is_static() GD_FINAL; virtual Visibility get_visibility() GD_FINAL; diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index 63bcfe053c..a84332d4cd 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -74,15 +74,14 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass); script_binding.wrapper_class = klass; script_binding.gchandle = MonoGCHandle::create_strong(managed); + script_binding.owner = unmanaged; - Reference *kref = Object::cast_to<Reference>(unmanaged); - if (kref) { + if (ref) { // Unsafe refcount increment. The managed instance also counts as a reference. // This way if the unmanaged world has no references to our owner // but the managed instance is alive, the refcount will be 1 instead of 0. // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) - - kref->reference(); + ref->reference(); } // The object was just created, no script instance binding should have been attached @@ -105,8 +104,6 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { ScriptInstance *si = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle); unmanaged->set_script_and_instance(script.get_ref_ptr(), si); - - return; } void unhandled_exception(MonoException *p_exc) { diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index 3191cdbd53..a6e04e561d 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -37,6 +37,7 @@ #include "core/os/os.h" #include "../godotsharp_dirs.h" +#include "../utils/string_utils.h" static int log_level_get_id(const char *p_log_level) { @@ -125,27 +126,6 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) { da->list_dir_end(); } -static String format(const char *p_fmt, ...) { - va_list args; - - va_start(args, p_fmt); - int len = vsnprintf(NULL, 0, p_fmt, args); - va_end(args); - - len += 1; // for the trailing '/0' - - char *buffer(memnew_arr(char, len)); - - va_start(args, p_fmt); - vsnprintf(buffer, len, p_fmt, args); - va_end(args); - - String res(buffer); - memdelete_arr(buffer); - - return res; -} - void GDMonoLog::initialize() { CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8(); @@ -172,7 +152,7 @@ void GDMonoLog::initialize() { OS::Time time_now = OS::get_singleton()->get_time(); int pid = OS::get_singleton()->get_process_id(); - String log_file_name = format("%d-%02d-%02d %02d:%02d:%02d (%d).txt", + String log_file_name = str_format("%d_%02d_%02d %02d.%02d.%02d (%d).txt", date_now.year, date_now.month, date_now.day, time_now.hour, time_now.min, time_now.sec, pid); diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index de4f3650bd..42102ed835 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -35,7 +35,7 @@ namespace GDMonoMarshal { -Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_export_info) { +Variant::Type managed_to_variant_type(const ManagedType &p_type) { switch (p_type.type_encoding) { case MONO_TYPE_BOOLEAN: return Variant::BOOL; @@ -157,64 +157,50 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_e return Variant::ARRAY; } + // The order in which we check the following interfaces is very important (dictionaries and generics first) + + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); + + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) { + return Variant::DICTIONARY; + } + if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) { return Variant::DICTIONARY; } + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) { + return Variant::ARRAY; + } + if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { return Variant::ARRAY; } } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); - - MonoException *exc = NULL; - GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary); - MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - - if (is_dict) { - if (r_export_info) { - MonoReflectionType *key_reftype; - MonoReflectionType *value_reftype; - - exc = NULL; - invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes), - reftype, &key_reftype, &value_reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - - r_export_info->dictionary.key_type = managed_to_variant_type(ManagedType::from_reftype(key_reftype)); - r_export_info->dictionary.value_type = managed_to_variant_type(ManagedType::from_reftype(value_reftype)); - } + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); + if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { return Variant::DICTIONARY; } - exc = NULL; - GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray); - MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - - if (is_array) { - if (r_export_info) { - MonoReflectionType *elem_reftype; - - exc = NULL; - invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType), - reftype, &elem_reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - - r_export_info->array.element_type = managed_to_variant_type(ManagedType::from_reftype(elem_reftype)); - } - + if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { return Variant::ARRAY; } + // The order in which we check the following interfaces is very important (dictionaries and generics first) + + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) + return Variant::DICTIONARY; + if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) { return Variant::DICTIONARY; } + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) + return Variant::ARRAY; + if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { return Variant::ARRAY; } @@ -228,6 +214,63 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_e return Variant::NIL; } +bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) { + switch (p_array_type.type_encoding) { + case MONO_TYPE_GENERICINST: { + MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type()); + + if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype)) { + MonoReflectionType *elem_reftype; + + GDMonoUtils::Marshal::array_get_element_type(array_reftype, &elem_reftype); + + r_elem_type = ManagedType::from_reftype(elem_reftype); + return true; + } + + MonoReflectionType *elem_reftype; + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(array_reftype, &elem_reftype)) { + r_elem_type = ManagedType::from_reftype(elem_reftype); + return true; + } + } break; + default: { + } break; + } + + return false; +} + +bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type) { + switch (p_dictionary_type.type_encoding) { + case MONO_TYPE_GENERICINST: { + MonoReflectionType *dict_reftype = mono_type_get_object(mono_domain_get(), p_dictionary_type.type_class->get_mono_type()); + + if (GDMonoUtils::Marshal::type_is_generic_dictionary(dict_reftype)) { + MonoReflectionType *key_reftype; + MonoReflectionType *value_reftype; + + GDMonoUtils::Marshal::dictionary_get_key_value_types(dict_reftype, &key_reftype, &value_reftype); + + r_key_type = ManagedType::from_reftype(key_reftype); + r_value_type = ManagedType::from_reftype(value_reftype); + return true; + } + + MonoReflectionType *key_reftype, *value_reftype; + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(dict_reftype, &key_reftype, &value_reftype)) { + r_key_type = ManagedType::from_reftype(key_reftype); + r_value_type = ManagedType::from_reftype(value_reftype); + return true; + } + } break; + default: { + } break; + } + + return false; +} + String mono_to_utf8_string(MonoString *p_mono_string) { MonoError error; char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error); @@ -494,12 +537,32 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); } + // The order in which we check the following interfaces is very important (dictionaries and generics first) + + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); + + MonoReflectionType *key_reftype, *value_reftype; + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) { + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), + GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype)); + } + if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) { return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary)); } + MonoReflectionType *elem_reftype; + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), + GDMonoUtils::Marshal::make_generic_array_type(elem_reftype)); + } + if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + if (GDMonoUtils::tools_godot_api_check()) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + } else { + return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array()); + } } } break; case MONO_TYPE_OBJECT: { @@ -593,32 +656,40 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); - - MonoException *exc = NULL; - GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary); - MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); - if (is_dict) { + if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class); } - exc = NULL; - GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray); - MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - - if (is_array) { + if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { return GDMonoUtils::create_managed_from(p_var->operator Array(), p_type.type_class); } + // The order in which we check the following interfaces is very important (dictionaries and generics first) + + MonoReflectionType *key_reftype, *value_reftype; + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) { + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), + GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype)); + } + if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) { return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary)); } + MonoReflectionType *elem_reftype; + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), + GDMonoUtils::Marshal::make_generic_array_type(elem_reftype)); + } + if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + if (GDMonoUtils::tools_godot_api_check()) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + } else { + return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array()); + } } } break; } break; @@ -768,77 +839,71 @@ Variant mono_object_to_variant(MonoObject *p_obj) { if (CACHED_CLASS(Array) == type_class) { MonoException *exc = NULL; Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return ptr ? Variant(*ptr) : Variant(); } if (CACHED_CLASS(Dictionary) == type_class) { MonoException *exc = NULL; Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return ptr ? Variant(*ptr) : Variant(); } + // The order in which we check the following interfaces is very important (dictionaries and generics first) + + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type()); + + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) { + return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj); + } + if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) { - Dictionary dict; - MonoException *exc = NULL; - invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary), p_obj, &dict, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return dict; + return GDMonoUtils::Marshal::idictionary_to_dictionary(p_obj); + } + + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) { + return GDMonoUtils::Marshal::enumerable_to_array(p_obj); } if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - Array array; - MonoException *exc = NULL; - invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray), p_obj, &array, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return array; + return GDMonoUtils::Marshal::enumerable_to_array(p_obj); } } break; case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); - - MonoException *exc = NULL; - - GDMonoUtils::TypeIsGenericDictionary type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary); - MonoBoolean is_dict = invoke_method_thunk(type_is_dict, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type()); - if (is_dict) { - exc = NULL; + if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { + MonoException *exc = NULL; MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return *unbox<Dictionary *>(ret); } - exc = NULL; - - GDMonoUtils::TypeIsGenericArray type_is_array = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray); - MonoBoolean is_array = invoke_method_thunk(type_is_array, reftype, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - - if (is_array) { - exc = NULL; + if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { + MonoException *exc = NULL; MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return *unbox<Array *>(ret); } + // The order in which we check the following interfaces is very important (dictionaries and generics first) + + if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) { + return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj); + } + if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) { - Dictionary dict; - exc = NULL; - invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary), p_obj, &dict, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return dict; + return GDMonoUtils::Marshal::idictionary_to_dictionary(p_obj); + } + + if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) { + return GDMonoUtils::Marshal::enumerable_to_array(p_obj); } if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) { - Array array; - exc = NULL; - invoke_method_thunk(CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray), p_obj, &array, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); - return array; + return GDMonoUtils::Marshal::enumerable_to_array(p_obj); } } break; } @@ -962,7 +1027,7 @@ MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) { for (int i = 0; i < p_array.size(); i++) { MonoString *boxed = mono_string_from_godot(r[i]); - mono_array_set(ret, MonoString *, i, boxed); + mono_array_setref(ret, i, boxed); } return ret; @@ -1067,4 +1132,5 @@ PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) { return ret; } + } // namespace GDMonoMarshal diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h index 4a73f9e3e6..3fa958ac32 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ b/modules/mono/mono_gd/gd_mono_marshal.h @@ -57,25 +57,10 @@ T unbox(MonoObject *p_obj) { #define BOX_PTR(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(IntPtr), x) #define BOX_ENUM(m_enum_class, x) mono_value_box(mono_domain_get(), m_enum_class, &x) -// FIXME: Made this struct in a hurry. It could be done differently. -struct ExportInfo { - struct ArrayInfo { - Variant::Type element_type; - - ArrayInfo() : - element_type(Variant::NIL) {} - } array; - struct DictionaryInfo { - Variant::Type key_type; - Variant::Type value_type; - - DictionaryInfo() : - key_type(Variant::NIL), - value_type(Variant::NIL) {} - } dictionary; -}; +Variant::Type managed_to_variant_type(const ManagedType &p_type); -Variant::Type managed_to_variant_type(const ManagedType &p_type, ExportInfo *r_export_info = NULL); +bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type); +bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type); // String diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp index 7f11e4671d..968b316a3e 100644 --- a/modules/mono/mono_gd/gd_mono_method.cpp +++ b/modules/mono/mono_gd/gd_mono_method.cpp @@ -74,6 +74,10 @@ void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) { method_info = MethodInfo(); } +GDMonoClass *GDMonoMethod::get_enclosing_class() const { + return GDMono::get_singleton()->get_class(mono_method_get_class(mono_method)); +} + bool GDMonoMethod::is_static() { return mono_method_get_flags(mono_method, NULL) & MONO_METHOD_ATTR_STATIC; } @@ -105,7 +109,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, for (int i = 0; i < params_count; i++) { MonoObject *boxed_param = GDMonoMarshal::variant_to_mono_object(p_params[i], param_types[i]); - mono_array_set(params, MonoObject *, i, boxed_param); + mono_array_setref(params, i, boxed_param); } MonoException *exc = NULL; diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h index f74cef438d..2fc8628f27 100644 --- a/modules/mono/mono_gd/gd_mono_method.h +++ b/modules/mono/mono_gd/gd_mono_method.h @@ -57,9 +57,11 @@ class GDMonoMethod : public IMonoClassMember { MonoMethod *mono_method; public: - virtual MemberType get_member_type() GD_FINAL { return MEMBER_TYPE_METHOD; } + virtual GDMonoClass *get_enclosing_class() const GD_FINAL; - virtual StringName get_name() GD_FINAL { return name; } + virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_METHOD; } + + virtual StringName get_name() const GD_FINAL { return name; } virtual bool is_static() GD_FINAL; diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp index 5842e26241..f1da00638f 100644 --- a/modules/mono/mono_gd/gd_mono_property.cpp +++ b/modules/mono/mono_gd/gd_mono_property.cpp @@ -142,7 +142,7 @@ bool GDMonoProperty::has_setter() { void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) { MonoMethod *prop_method = mono_property_get_set_method(mono_property); MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1); - mono_array_set(params, MonoObject *, 0, p_value); + mono_array_setref(params, 0, p_value); MonoException *exc = NULL; GDMonoUtils::runtime_invoke_array(prop_method, p_object, params, &exc); if (exc) { diff --git a/modules/mono/mono_gd/gd_mono_property.h b/modules/mono/mono_gd/gd_mono_property.h index 2700c460b0..d6efa60412 100644 --- a/modules/mono/mono_gd/gd_mono_property.h +++ b/modules/mono/mono_gd/gd_mono_property.h @@ -47,9 +47,11 @@ class GDMonoProperty : public IMonoClassMember { MonoCustomAttrInfo *attributes; public: - virtual MemberType get_member_type() GD_FINAL { return MEMBER_TYPE_PROPERTY; } + virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; } - virtual StringName get_name() GD_FINAL { return name; } + virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_PROPERTY; } + + virtual StringName get_name() const GD_FINAL { return name; } virtual bool is_static() GD_FINAL; virtual Visibility get_visibility() GD_FINAL; diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index bcf5712d16..5987fa8ebb 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -50,6 +50,7 @@ MonoCache mono_cache; #define CACHE_AND_CHECK(m_var, m_val) \ { \ + CRASH_COND(m_var != NULL); \ m_var = m_val; \ if (!m_var) { \ ERR_EXPLAIN("Mono Cache: Member " #m_var " is null"); \ @@ -65,7 +66,9 @@ MonoCache mono_cache; #define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method, m_val) #define CACHE_PROPERTY_AND_CHECK(m_class, m_property, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.property_##m_class##_##m_property, m_val) -void MonoCache::clear_members() { +void MonoCache::clear_corlib_cache() { + + corlib_cache_updated = false; class_MonoObject = NULL; class_bool = NULL; @@ -93,6 +96,11 @@ void MonoCache::clear_members() { #endif class_KeyNotFoundException = NULL; +} + +void MonoCache::clear_godot_api_cache() { + + godot_api_cache_updated = false; rawclass_Dictionary = NULL; @@ -109,7 +117,7 @@ void MonoCache::clear_members() { class_NodePath = NULL; class_RID = NULL; class_GodotObject = NULL; - class_GodotReference = NULL; + class_GodotResource = NULL; class_Node = NULL; class_Control = NULL; class_Spatial = NULL; @@ -117,6 +125,7 @@ void MonoCache::clear_members() { class_Array = NULL; class_Dictionary = NULL; class_MarshalUtils = NULL; + class_ISerializationListener = NULL; #ifdef DEBUG_ENABLED class_DebuggingUtils = NULL; @@ -151,20 +160,29 @@ void MonoCache::clear_members() { methodthunk_SignalAwaiter_FailureCallback = NULL; methodthunk_GodotTaskScheduler_Activate = NULL; + // Start of MarshalUtils methods + methodthunk_MarshalUtils_TypeIsGenericArray = NULL; methodthunk_MarshalUtils_TypeIsGenericDictionary = NULL; + methodthunk_MarshalUtils_ArrayGetElementType = NULL; methodthunk_MarshalUtils_DictionaryGetKeyValueTypes = NULL; + + methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType = NULL; + methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType = NULL; + methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info = NULL; + methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info = NULL; + + methodthunk_MarshalUtils_MakeGenericArrayType = NULL; + methodthunk_MarshalUtils_MakeGenericDictionaryType = NULL; + methodthunk_MarshalUtils_EnumerableToArray = NULL; methodthunk_MarshalUtils_IDictionaryToDictionary = NULL; + methodthunk_MarshalUtils_GenericIDictionaryToDictionary = NULL; - task_scheduler_handle = Ref<MonoGCHandle>(); -} - -void MonoCache::cleanup() { + // End of MarshalUtils methods - corlib_cache_updated = false; - godot_api_cache_updated = false; + task_scheduler_handle = Ref<MonoGCHandle>(); } #define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) @@ -217,7 +235,7 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath)); CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(RID)); CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object)); - CACHE_CLASS_AND_CHECK(GodotReference, GODOT_API_CLASS(Reference)); + CACHE_CLASS_AND_CHECK(GodotResource, GODOT_API_CLASS(Resource)); CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node)); CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial)); @@ -225,6 +243,7 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)); CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)); CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); + CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener)); #ifdef DEBUG_ENABLED CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils)); @@ -258,30 +277,40 @@ void update_godot_api_cache() { CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method_thunk("FailureCallback", 0)); CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method_thunk("Activate", 0)); + // Start of MarshalUtils methods + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericArray, (TypeIsGenericArray)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("TypeIsGenericArray", 1)); CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericDictionary, (TypeIsGenericDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("TypeIsGenericDictionary", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArrayGetElementType, (ArrayGetElementType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("ArrayGetElementType", 2)); CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryGetKeyValueTypes, (DictionaryGetKeyValueTypes)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("DictionaryGetKeyValueTypes", 3)); + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType, (GenericIEnumerableIsAssignableFromType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIEnumerableIsAssignableFromType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType, (GenericIDictionaryIsAssignableFromType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryIsAssignableFromType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info, (GenericIEnumerableIsAssignableFromType_with_info)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIEnumerableIsAssignableFromType", 2)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info, (GenericIDictionaryIsAssignableFromType_with_info)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryIsAssignableFromType", 3)); + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericArrayType, (MakeGenericArrayType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("MakeGenericArrayType", 1)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericDictionaryType, (MakeGenericDictionaryType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("MakeGenericDictionaryType", 2)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, EnumerableToArray, (EnumerableToArray)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("EnumerableToArray", 2)); CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IDictionaryToDictionary, (IDictionaryToDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IDictionaryToDictionary", 2)); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryToDictionary, (GenericIDictionaryToDictionary)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("GenericIDictionaryToDictionary", 2)); + + // End of MarshalUtils methods #ifdef DEBUG_ENABLED CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method_thunk("GetStackFrameInfo", 4)); #endif // TODO Move to CSharpLanguage::init() and do handle disposal - MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); - GDMonoUtils::runtime_object_init(task_scheduler); + MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); + GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler)); mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); mono_cache.godot_api_cache_updated = true; } -void clear_cache() { - mono_cache.cleanup(); - mono_cache.clear_members(); -} - MonoObject *unmanaged_get_managed(Object *unmanaged) { if (!unmanaged) @@ -344,7 +373,6 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { // This way if the unmanaged world has no references to our owner // but the managed instance is alive, the refcount will be 1 instead of 0. // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) - ref->reference(); } @@ -357,7 +385,7 @@ void set_main_thread(MonoThread *p_thread) { void attach_current_thread() { ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN); + MonoThread *mono_thread = mono_thread_attach(mono_domain_get()); ERR_FAIL_NULL(mono_thread); } @@ -372,11 +400,10 @@ MonoThread *get_current_thread() { return mono_thread_current(); } -void runtime_object_init(MonoObject *p_this_obj) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - // FIXME: Do not use mono_runtime_object_init, it aborts if an exception is thrown - mono_runtime_object_init(p_this_obj); - GD_MONO_END_RUNTIME_INVOKE; +void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) { + GDMonoMethod *ctor = p_class->get_method(".ctor", 0); + ERR_FAIL_NULL(ctor); + ctor->invoke_raw(p_this_obj, NULL, r_exc); } GDMonoClass *get_object_class(MonoObject *p_object) { @@ -422,33 +449,28 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) { } MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) { - String object_type = p_object->get_class_name(); - - if (object_type[0] == '_') - object_type = object_type.substr(1, object_type.length()); - - if (!ClassDB::is_parent_class(object_type, p_native)) { + if (!ClassDB::is_parent_class(p_object->get_class_name(), p_native)) { ERR_EXPLAIN("Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'"); ERR_FAIL_V(NULL); } - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); // Construct - GDMonoUtils::runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object, p_class); return mono_object; } MonoObject *create_managed_from(const NodePath &p_from) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(NodePath)); + MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath)); ERR_FAIL_NULL_V(mono_object, NULL); // Construct - GDMonoUtils::runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(NodePath)); CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from))); @@ -456,11 +478,11 @@ MonoObject *create_managed_from(const NodePath &p_from) { } MonoObject *create_managed_from(const RID &p_from) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(RID)); + MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID)); ERR_FAIL_NULL_V(mono_object, NULL); // Construct - GDMonoUtils::runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(RID)); CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from))); @@ -468,7 +490,7 @@ MonoObject *create_managed_from(const RID &p_from) { } MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); // Search constructor that takes a pointer as parameter @@ -492,13 +514,13 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { MonoException *exc = NULL; GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return mono_object; } MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) { - MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); // Search constructor that takes a pointer as parameter @@ -522,7 +544,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) MonoException *exc = NULL; GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNLIKELY_UNHANDLED_EXCEPTION(exc); + UNHANDLED_EXCEPTION(exc); return mono_object; } @@ -641,7 +663,10 @@ void print_unhandled_exception(MonoException *p_exc) { } void set_pending_exception(MonoException *p_exc) { -#ifdef HAS_PENDING_EXCEPTIONS +#ifdef NO_PENDING_EXCEPTIONS + debug_unhandled_exception(p_exc); + GD_UNREACHABLE(); +#else if (get_runtime_invoke_count() == 0) { debug_unhandled_exception(p_exc); GD_UNREACHABLE(); @@ -651,9 +676,6 @@ void set_pending_exception(MonoException *p_exc) { ERR_PRINTS("Exception thrown from managed code, but it could not be set as pending:"); GDMonoUtils::debug_print_unhandled_exception(p_exc); } -#else - debug_unhandled_exception(p_exc); - GD_UNREACHABLE(); #endif } @@ -727,4 +749,139 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc) { invoke_method_thunk(CACHED_METHOD_THUNK(GodotObject, Dispose), p_mono_object, r_exc); } +namespace Marshal { + +#ifdef MONO_GLUE_ENABLED +#ifdef TOOLS_ENABLED +#define NO_GLUE_RET(m_ret) \ + { \ + if (!mono_cache.godot_api_cache_updated) return m_ret; \ + } +#else +#define NO_GLUE_RET(m_ret) \ + {} +#endif +#else +#define NO_GLUE_RET(m_ret) \ + { return m_ret; } +#endif + +bool type_is_generic_array(MonoReflectionType *p_reftype) { + NO_GLUE_RET(false); + TypeIsGenericArray thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray); + MonoException *exc = NULL; + MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return (bool)res; +} + +bool type_is_generic_dictionary(MonoReflectionType *p_reftype) { + NO_GLUE_RET(false); + TypeIsGenericDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary); + MonoException *exc = NULL; + MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return (bool)res; +} + +void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) { + ArrayGetElementType thunk = CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType); + MonoException *exc = NULL; + invoke_method_thunk(thunk, p_array_reftype, r_elem_reftype, &exc); + UNHANDLED_EXCEPTION(exc); +} + +void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { + DictionaryGetKeyValueTypes thunk = CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes); + MonoException *exc = NULL; + invoke_method_thunk(thunk, p_dict_reftype, r_key_reftype, r_value_reftype, &exc); + UNHANDLED_EXCEPTION(exc); +} + +bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) { + NO_GLUE_RET(false); + GenericIEnumerableIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType); + MonoException *exc = NULL; + MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return (bool)res; +} + +bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) { + NO_GLUE_RET(false); + GenericIDictionaryIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType); + MonoException *exc = NULL; + MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return (bool)res; +} + +bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) { + NO_GLUE_RET(false); + GenericIEnumerableIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info); + MonoException *exc = NULL; + MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_elem_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return (bool)res; +} + +bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { + NO_GLUE_RET(false); + GenericIDictionaryIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info); + MonoException *exc = NULL; + MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_key_reftype, r_value_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return (bool)res; +} + +Array enumerable_to_array(MonoObject *p_enumerable) { + NO_GLUE_RET(Array()); + Array result; + EnumerableToArray thunk = CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray); + MonoException *exc = NULL; + invoke_method_thunk(thunk, p_enumerable, &result, &exc); + UNHANDLED_EXCEPTION(exc); + return result; +} + +Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) { + NO_GLUE_RET(Dictionary()); + Dictionary result; + IDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary); + MonoException *exc = NULL; + invoke_method_thunk(thunk, p_idictionary, &result, &exc); + UNHANDLED_EXCEPTION(exc); + return result; +} + +Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary) { + NO_GLUE_RET(Dictionary()); + Dictionary result; + GenericIDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary); + MonoException *exc = NULL; + invoke_method_thunk(thunk, p_generic_idictionary, &result, &exc); + UNHANDLED_EXCEPTION(exc); + return result; +} + +GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) { + NO_GLUE_RET(NULL); + MakeGenericArrayType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType); + MonoException *exc = NULL; + MonoReflectionType *reftype = invoke_method_thunk(thunk, p_elem_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); +} + +GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { + NO_GLUE_RET(NULL); + MakeGenericDictionaryType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType); + MonoException *exc = NULL; + MonoReflectionType *reftype = invoke_method_thunk(thunk, p_key_reftype, p_value_reftype, &exc); + UNHANDLED_EXCEPTION(exc); + return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); +} + +} // namespace Marshal + } // namespace GDMonoUtils diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index 87610e286c..f535fbb6d0 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -41,7 +41,7 @@ #include "core/object.h" #include "core/reference.h" -#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \ +#define UNHANDLED_EXCEPTION(m_exc) \ if (unlikely(m_exc != NULL)) { \ GDMonoUtils::debug_unhandled_exception(m_exc); \ GD_UNREACHABLE(); \ @@ -60,10 +60,45 @@ typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, Mo typedef MonoBoolean (*TypeIsGenericArray)(MonoReflectionType *, MonoException **); typedef MonoBoolean (*TypeIsGenericDictionary)(MonoReflectionType *, MonoException **); -typedef MonoBoolean (*ArrayGetElementType)(MonoReflectionType *, MonoReflectionType **, MonoException **); -typedef MonoBoolean (*DictionaryGetKeyValueTypes)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **); + +typedef void (*ArrayGetElementType)(MonoReflectionType *, MonoReflectionType **, MonoException **); +typedef void (*DictionaryGetKeyValueTypes)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **); + +typedef MonoBoolean (*GenericIEnumerableIsAssignableFromType)(MonoReflectionType *, MonoException **); +typedef MonoBoolean (*GenericIDictionaryIsAssignableFromType)(MonoReflectionType *, MonoException **); +typedef MonoBoolean (*GenericIEnumerableIsAssignableFromType_with_info)(MonoReflectionType *, MonoReflectionType **, MonoException **); +typedef MonoBoolean (*GenericIDictionaryIsAssignableFromType_with_info)(MonoReflectionType *, MonoReflectionType **, MonoReflectionType **, MonoException **); + +typedef MonoReflectionType *(*MakeGenericArrayType)(MonoReflectionType *, MonoException **); +typedef MonoReflectionType *(*MakeGenericDictionaryType)(MonoReflectionType *, MonoReflectionType *, MonoException **); + typedef void (*EnumerableToArray)(MonoObject *, Array *, MonoException **); typedef void (*IDictionaryToDictionary)(MonoObject *, Dictionary *, MonoException **); +typedef void (*GenericIDictionaryToDictionary)(MonoObject *, Dictionary *, MonoException **); + +namespace Marshal { + +bool type_is_generic_array(MonoReflectionType *p_reftype); +bool type_is_generic_dictionary(MonoReflectionType *p_reftype); + +void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype); +void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype); + +bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype); +bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype); +bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype); +bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype); + +GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype); +GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); + +Array enumerable_to_array(MonoObject *p_enumerable); +Dictionary idictionary_to_dictionary(MonoObject *p_idictionary); +Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary); + +} // namespace Marshal + +// End of MarshalUtils methods struct MonoCache { @@ -114,7 +149,7 @@ struct MonoCache { GDMonoClass *class_NodePath; GDMonoClass *class_RID; GDMonoClass *class_GodotObject; - GDMonoClass *class_GodotReference; + GDMonoClass *class_GodotResource; GDMonoClass *class_Node; GDMonoClass *class_Control; GDMonoClass *class_Spatial; @@ -122,6 +157,7 @@ struct MonoCache { GDMonoClass *class_Array; GDMonoClass *class_Dictionary; GDMonoClass *class_MarshalUtils; + GDMonoClass *class_ISerializationListener; #ifdef DEBUG_ENABLED GDMonoClass *class_DebuggingUtils; @@ -156,26 +192,39 @@ struct MonoCache { SignalAwaiter_FailureCallback methodthunk_SignalAwaiter_FailureCallback; GodotTaskScheduler_Activate methodthunk_GodotTaskScheduler_Activate; + // Start of MarshalUtils methods + TypeIsGenericArray methodthunk_MarshalUtils_TypeIsGenericArray; TypeIsGenericDictionary methodthunk_MarshalUtils_TypeIsGenericDictionary; + ArrayGetElementType methodthunk_MarshalUtils_ArrayGetElementType; DictionaryGetKeyValueTypes methodthunk_MarshalUtils_DictionaryGetKeyValueTypes; + + GenericIEnumerableIsAssignableFromType methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType; + GenericIDictionaryIsAssignableFromType methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType; + GenericIEnumerableIsAssignableFromType_with_info methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info; + GenericIDictionaryIsAssignableFromType_with_info methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info; + + MakeGenericArrayType methodthunk_MarshalUtils_MakeGenericArrayType; + MakeGenericDictionaryType methodthunk_MarshalUtils_MakeGenericDictionaryType; + EnumerableToArray methodthunk_MarshalUtils_EnumerableToArray; IDictionaryToDictionary methodthunk_MarshalUtils_IDictionaryToDictionary; + GenericIDictionaryToDictionary methodthunk_MarshalUtils_GenericIDictionaryToDictionary; + + // End of MarshalUtils methods Ref<MonoGCHandle> task_scheduler_handle; bool corlib_cache_updated; bool godot_api_cache_updated; - void clear_members(); - void cleanup(); + void clear_corlib_cache(); + void clear_godot_api_cache(); MonoCache() { - corlib_cache_updated = false; - godot_api_cache_updated = false; - - clear_members(); + clear_corlib_cache(); + clear_godot_api_cache(); } }; @@ -183,7 +232,22 @@ extern MonoCache mono_cache; void update_corlib_cache(); void update_godot_api_cache(); -void clear_cache(); + +inline void clear_corlib_cache() { + mono_cache.clear_corlib_cache(); +} + +inline void clear_godot_api_cache() { + mono_cache.clear_godot_api_cache(); +} + +_FORCE_INLINE_ bool tools_godot_api_check() { +#ifdef TOOLS_ENABLED + return mono_cache.godot_api_cache_updated; +#else + return true; // Assume it's updated if this was called, otherwise it's a bug +#endif +} _FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) { p_hash ^= p_with_hash + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2); @@ -205,7 +269,7 @@ _FORCE_INLINE_ bool is_main_thread() { return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current(); } -void runtime_object_init(MonoObject *p_this_obj); +void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = NULL); GDMonoClass *get_object_class(MonoObject *p_object); GDMonoClass *type_get_proxy_class(const StringName &p_type); diff --git a/modules/mono/mono_gd/i_mono_class_member.h b/modules/mono/mono_gd/i_mono_class_member.h index 553d9edc72..f4de4e3230 100644 --- a/modules/mono/mono_gd/i_mono_class_member.h +++ b/modules/mono/mono_gd/i_mono_class_member.h @@ -53,9 +53,11 @@ public: virtual ~IMonoClassMember() {} - virtual MemberType get_member_type() = 0; + virtual GDMonoClass *get_enclosing_class() const = 0; - virtual StringName get_name() = 0; + virtual MemberType get_member_type() const = 0; + + virtual StringName get_name() const = 0; virtual bool is_static() = 0; diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 5d37e8212f..54d73c971f 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -91,11 +91,11 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc set_completed(true); int signal_argc = p_argcount - 1; - MonoArray *signal_args = mono_array_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(MonoObject), signal_argc); + MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), signal_argc); for (int i = 0; i < signal_argc; i++) { MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]); - mono_array_set(signal_args, MonoObject *, i, boxed); + mono_array_setref(signal_args, i, boxed); } MonoException *exc = NULL; diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h index 098008ded7..4fb3cdb56d 100644 --- a/modules/mono/signal_awaiter_utils.h +++ b/modules/mono/signal_awaiter_utils.h @@ -41,7 +41,7 @@ Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p class SignalAwaiterHandle : public MonoGCHandle { - GDCLASS(SignalAwaiterHandle, MonoGCHandle) + GDCLASS(SignalAwaiterHandle, MonoGCHandle); bool completed; diff --git a/modules/mono/editor/monodevelop_instance.cpp b/modules/mono/utils/android_utils.cpp index 3caa56d1d0..7dd67e3b8e 100644 --- a/modules/mono/editor/monodevelop_instance.cpp +++ b/modules/mono/utils/android_utils.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* monodevelop_instance.cpp */ +/* android_utils.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,58 +28,41 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "monodevelop_instance.h" +#include "android_utils.h" -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_class.h" +#ifdef __ANDROID__ -void MonoDevelopInstance::execute(const Vector<String> &p_files) { +#include "platform/android/thread_jandroid.h" - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) +namespace GDMonoUtils { +namespace Android { - ERR_FAIL_NULL(execute_method); - ERR_FAIL_COND(gc_handle.is_null()); +String get_app_native_lib_dir() { + JNIEnv *env = ThreadAndroid::get_env(); - MonoException *exc = NULL; + jclass activityThreadClass = env->FindClass("android/app/ActivityThread"); + jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;"); + jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread); + jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;"); + jobject ctx = env->CallObjectMethod(activityThread, getApplication); - Variant files = p_files; - const Variant *args[1] = { &files }; - execute_method->invoke(gc_handle->get_target(), args, &exc); + jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); + jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo); + jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;"); + jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField); - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } -} + String result; -void MonoDevelopInstance::execute(const String &p_file) { + const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL); + if (nativeLibraryDir_utf8) { + result.parse_utf8(nativeLibraryDir_utf8); + env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8); + } - Vector<String> files; - files.push_back(p_file); - execute(files); + return result; } -MonoDevelopInstance::MonoDevelopInstance(const String &p_solution, EditorId p_editor_id) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "MonoDevelopInstance"); - - MonoObject *obj = mono_object_new(TOOLS_DOMAIN, klass->get_mono_ptr()); +} // namespace Android +} // namespace GDMonoUtils - GDMonoMethod *ctor = klass->get_method(".ctor", 2); - MonoException *exc = NULL; - - Variant solution = p_solution; - Variant editor_id = p_editor_id; - const Variant *args[2] = { &solution, &editor_id }; - ctor->invoke(obj, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } - - gc_handle = MonoGCHandle::create_strong(obj); - execute_method = klass->get_method("Execute", 1); -} +#endif // __ANDROID__ diff --git a/modules/mono/editor/mono_build_info.h b/modules/mono/utils/android_utils.h index b0ae2ed52e..f911c3fdfe 100644 --- a/modules/mono/editor/mono_build_info.h +++ b/modules/mono/utils/android_utils.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* mono_build_info.h */ +/* android_utils.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,28 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef MONO_BUILD_INFO_H -#define MONO_BUILD_INFO_H +#ifndef ANDROID_UTILS_H +#define ANDROID_UTILS_H -#include "core/ustring.h" -#include "core/vector.h" - -struct MonoBuildInfo { +#ifdef __ANDROID__ - struct Hasher { - static uint32_t hash(const MonoBuildInfo &p_key); - }; +#include "core/ustring.h" - String solution; - String configuration; - Vector<String> custom_props; +namespace GDMonoUtils { +namespace Android { - bool operator==(const MonoBuildInfo &p_b) const; +String get_app_native_lib_dir(); - String get_log_dirpath(); +} // namespace Android +} // namespace GDMonoUtils - MonoBuildInfo(); - MonoBuildInfo(const String &p_solution, const String &p_config); -}; +#endif // __ANDROID__ -#endif // MONO_BUILD_INFO_H +#endif // ANDROID_UTILS_H diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index 6e431f51e7..20863b1afe 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -36,16 +36,21 @@ #include "core/project_settings.h" #ifdef WINDOWS_ENABLED +#include <windows.h> + #define ENV_PATH_SEP ";" #else -#define ENV_PATH_SEP ":" #include <limits.h> +#include <unistd.h> + +#define ENV_PATH_SEP ":" #endif #include <stdlib.h> -String path_which(const String &p_name) { +namespace path { +String find_executable(const String &p_name) { #ifdef WINDOWS_ENABLED Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false); #endif @@ -55,7 +60,7 @@ String path_which(const String &p_name) { return String(); for (int i = 0; i < env_path.size(); i++) { - String p = path_join(env_path[i], p_name); + String p = path::join(env_path[i], p_name); #ifdef WINDOWS_ENABLED for (int j = 0; j < exts.size(); j++) { @@ -73,42 +78,96 @@ String path_which(const String &p_name) { return String(); } -void fix_path(const String &p_path, String &r_out) { - r_out = p_path.replace("\\", "/"); +String cwd() { +#ifdef WINDOWS_ENABLED + const DWORD expected_size = ::GetCurrentDirectoryW(0, NULL); + + String buffer; + buffer.resize((int)expected_size); + if (::GetCurrentDirectoryW(expected_size, buffer.ptrw()) == 0) + return "."; + + return buffer.simplify_path(); +#else + char buffer[PATH_MAX]; + if (::getcwd(buffer, sizeof(buffer)) == NULL) + return "."; + + String result; + if (result.parse_utf8(buffer)) + return "."; - while (true) { // in case of using 2 or more slash - String compare = r_out.replace("//", "/"); - if (r_out == compare) - break; - else - r_out = compare; + return result.simplify_path(); +#endif +} + +String abspath(const String &p_path) { + if (p_path.is_abs_path()) { + return p_path.simplify_path(); + } else { + return path::join(path::cwd(), p_path).simplify_path(); } } -bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) { +String realpath(const String &p_path) { #ifdef WINDOWS_ENABLED - CharType ret[_MAX_PATH]; - if (::_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) { - String abspath = String(ret).replace("\\", "/"); - int pos = abspath.find(":/"); - if (pos != -1) { - r_abs_path = abspath.substr(pos - 1, abspath.length()); - } else { - r_abs_path = abspath; - } - return true; - } -#else - char *resolved_path = ::realpath(p_existing_path.utf8().get_data(), NULL); - if (resolved_path) { - String retstr; - bool success = !retstr.parse_utf8(resolved_path); - ::free(resolved_path); - if (success) { - r_abs_path = retstr; - return true; - } + // Open file without read/write access + HANDLE hFile = ::CreateFileW(p_path.c_str(), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return p_path; + + const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, NULL, 0, FILE_NAME_NORMALIZED); + + if (expected_size == 0) { + ::CloseHandle(hFile); + return p_path; } + + String buffer; + buffer.resize((int)expected_size); + ::GetFinalPathNameByHandleW(hFile, buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED); + + ::CloseHandle(hFile); + return buffer.simplify_path(); +#elif UNIX_ENABLED + char *resolved_path = ::realpath(p_path.utf8().get_data(), NULL); + + if (!resolved_path) + return p_path; + + String result; + bool parse_ok = result.parse_utf8(resolved_path); + ::free(resolved_path); + + if (parse_ok) + return p_path; + + return result.simplify_path(); #endif - return false; } + +String join(const String &p_a, const String &p_b) { + if (p_a.empty()) + return p_b; + + const CharType a_last = p_a[p_a.length() - 1]; + if ((a_last == '/' || a_last == '\\') || + (p_b.size() > 0 && (p_b[0] == '/' || p_b[0] == '\\'))) { + return p_a + p_b; + } + + return p_a + "/" + p_b; +} + +String join(const String &p_a, const String &p_b, const String &p_c) { + return path::join(path::join(p_a, p_b), p_c); +} + +String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d) { + return path::join(path::join(path::join(p_a, p_b), p_c), p_d); +} + +} // namespace path diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h index 69edf4deb7..ca25bc09f7 100644 --- a/modules/mono/utils/path_utils.h +++ b/modules/mono/utils/path_utils.h @@ -31,24 +31,32 @@ #ifndef PATH_UTILS_H #define PATH_UTILS_H +#include "core/string_builder.h" #include "core/ustring.h" -_FORCE_INLINE_ String path_join(const String &e1, const String &e2) { - return e1.plus_file(e2); -} +namespace path { -_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3) { - return e1.plus_file(e2).plus_file(e3); -} +String join(const String &p_a, const String &p_b); +String join(const String &p_a, const String &p_b, const String &p_c); +String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d); -_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3, const String &e4) { - return e1.plus_file(e2).plus_file(e3).plus_file(e4); -} +String find_executable(const String &p_name); -String path_which(const String &p_name); +/// Returns a normalized absolute path to the current working directory +String cwd(); -void fix_path(const String &p_path, String &r_out); +/** + * Obtains a normalized absolute path to p_path. Symbolic links are + * not resolved. The path p_path might not exist in the file system. + */ +String abspath(const String &p_path); -bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path); +/** + * Obtains a normalized path to p_path with symbolic links resolved. + * The resulting path might be either a relative or an absolute path. + */ +String realpath(const String &p_path); + +} // namespace path #endif // PATH_UTILS_H diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index c390f8b9c2..2b014c2a45 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -32,6 +32,9 @@ #include "core/os/file_access.h" +#include <stdio.h> +#include <stdlib.h> + namespace { int sfind(const String &p_text, int p_from) { @@ -41,7 +44,7 @@ int sfind(const String &p_text, int p_from) { int src_len = 2; int len = p_text.length(); - if (src_len == 0 || len == 0) + if (len == 0) return -1; const CharType *src = p_text.c_str(); @@ -63,7 +66,7 @@ int sfind(const String &p_text, int p_from) { break; case 1: { CharType c = src[read_pos]; - found = src[read_pos] == 's' || (c >= '0' || c <= '4'); + found = src[read_pos] == 's' || (c >= '0' && c <= '4'); break; } default: @@ -184,3 +187,50 @@ Error read_all_file_utf8(const String &p_path, String &r_content) { r_content = source; return OK; } + +// TODO: Move to variadic templates once we upgrade to C++11 + +String str_format(const char *p_format, ...) { + va_list list; + + va_start(list, p_format); + String res = str_format(p_format, list); + va_end(list); + + return res; +} +// va_copy was defined in the C99, but not in C++ standards before C++11. +// When you compile C++ without --std=c++<XX> option, compilers still define +// va_copy, otherwise you have to use the internal version (__va_copy). +#if !defined(va_copy) +#if defined(__GNUC__) +#define va_copy(d, s) __va_copy((d), (s)) +#else +#define va_copy(d, s) ((d) = (s)) +#endif +#endif + +#if defined(MINGW_ENABLED) || defined(_MSC_VER) && _MSC_VER < 1900 +#define vsnprintf(m_buffer, m_count, m_format, m_argptr) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_argptr) +#endif + +String str_format(const char *p_format, va_list p_list) { + va_list list; + + va_copy(list, p_list); + int len = vsnprintf(NULL, 0, p_format, list); + va_end(list); + + len += 1; // for the trailing '/0' + + char *buffer(memnew_arr(char, len)); + + va_copy(list, p_list); + vsnprintf(buffer, len, p_format, list); + va_end(list); + + String res(buffer); + memdelete_arr(buffer); + + return res; +} diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h index 61765ccfd8..565b9bb644 100644 --- a/modules/mono/utils/string_utils.h +++ b/modules/mono/utils/string_utils.h @@ -34,6 +34,8 @@ #include "core/ustring.h" #include "core/variant.h" +#include <stdarg.h> + String sformat(const String &p_text, const Variant &p1 = Variant(), const Variant &p2 = Variant(), const Variant &p3 = Variant(), const Variant &p4 = Variant(), const Variant &p5 = Variant()); #ifdef TOOLS_ENABLED @@ -44,4 +46,15 @@ String escape_csharp_keyword(const String &p_name); Error read_all_file_utf8(const String &p_path, String &r_content); +#if defined(__GNUC__) +#define _PRINTF_FORMAT_ATTRIBUTE_1_0 __attribute__((format(printf, 1, 0))) +#define _PRINTF_FORMAT_ATTRIBUTE_1_2 __attribute__((format(printf, 1, 2))) +#else +#define _PRINTF_FORMAT_ATTRIBUTE_1_0 +#define _PRINTF_FORMAT_ATTRIBUTE_1_2 +#endif + +String str_format(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_1_2; +String str_format(const char *p_format, va_list p_list) _PRINTF_FORMAT_ATTRIBUTE_1_0; + #endif // STRING_FORMAT_H diff --git a/modules/mono/utils/thread_local.h b/modules/mono/utils/thread_local.h index 488cc2619a..e52b6e73ef 100644 --- a/modules/mono/utils/thread_local.h +++ b/modules/mono/utils/thread_local.h @@ -76,7 +76,7 @@ struct ThreadLocalStorage { void *get_value() const; void set_value(void *p_value) const; - void alloc(void(_CALLBACK_FUNC_ *p_dest_callback)(void *)); + void alloc(void(_CALLBACK_FUNC_ *p_destr_callback)(void *)); void free(); private: |