diff options
Diffstat (limited to 'modules/mono')
107 files changed, 6571 insertions, 2641 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub index 41be367f2f..c723b210cb 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -1,54 +1,57 @@ #!/usr/bin/env python -import build_scripts.tls_configure as tls_configure import build_scripts.mono_configure as mono_configure -Import('env') -Import('env_modules') +Import("env") +Import("env_modules") env_mono = env_modules.Clone() -if env_mono['tools']: +if env_mono["tools"]: # NOTE: It is safe to generate this file here, since this is still executed serially import build_scripts.gen_cs_glue_version as gen_cs_glue_version - gen_cs_glue_version.generate_header('glue/GodotSharp', 'glue/cs_glue_version.gen.h') + + gen_cs_glue_version.generate_header("glue/GodotSharp", "glue/cs_glue_version.gen.h") # Glue sources -if env_mono['mono_glue']: - env_mono.Append(CPPDEFINES=['MONO_GLUE_ENABLED']) +if env_mono["mono_glue"]: + env_mono.Append(CPPDEFINES=["MONO_GLUE_ENABLED"]) import os.path - if not os.path.isfile('glue/mono_glue.gen.cpp'): - raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?") -if env_mono['tools'] or env_mono['target'] != 'release': - env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD']) - -# Configure Thread Local Storage + if not os.path.isfile("glue/mono_glue.gen.cpp"): + raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?") -conf = Configure(env_mono) -tls_configure.configure(conf) -env_mono = conf.Finish() +if env_mono["tools"] or env_mono["target"] != "release": + env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) # Configure Mono mono_configure.configure(env, env_mono) -if env_mono['tools'] and env_mono['mono_glue']: +if env_mono["tools"] and env_mono["mono_glue"]: # Build Godot API solution import build_scripts.api_solution_build as api_solution_build + api_sln_cmd = api_solution_build.build(env_mono) # Build GodotTools import build_scripts.godot_tools_build as godot_tools_build + godot_tools_build.build(env_mono, api_sln_cmd) # Add sources -env_mono.add_source_files(env.modules_sources, '*.cpp') -env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') -env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp') -env_mono.add_source_files(env.modules_sources, 'utils/*.cpp') +env_mono.add_source_files(env.modules_sources, "*.cpp") +env_mono.add_source_files(env.modules_sources, "glue/*.cpp") +env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp") +env_mono.add_source_files(env.modules_sources, "utils/*.cpp") + +env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.cpp") + +if env["platform"] in ["osx", "iphone"]: + env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.mm") + env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.m") -if env['tools']: - env_mono.add_source_files(env.modules_sources, 'editor/*.cpp') +if env["tools"]: + env_mono.add_source_files(env.modules_sources, "editor/*.cpp") diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py index 639197c285..9abac22df6 100644 --- a/modules/mono/build_scripts/api_solution_build.py +++ b/modules/mono/build_scripts/api_solution_build.py @@ -8,21 +8,22 @@ from SCons.Script import Dir def build_api_solution(source, target, env): # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - module_dir = env['module_dir'] + module_dir = env["module_dir"] - solution_path = os.path.join(module_dir, 'glue/GodotSharp/GodotSharp.sln') + solution_path = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") - build_config = env['solution_build_config'] + build_config = env["solution_build_config"] - extra_msbuild_args = ['/p:NoWarn=1591'] # Ignore missing documentation warnings + extra_msbuild_args = ["/p:NoWarn=1591"] # Ignore missing documentation warnings from .solution_builder import build_solution + build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args) # Copy targets - core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharp', 'bin', build_config)) - editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharpEditor', 'bin', build_config)) + core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharp", "bin", build_config)) + editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharpEditor", "bin", build_config)) dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) @@ -32,6 +33,7 @@ def build_api_solution(source, target, env): def copy_target(target_path): from shutil import copy + filename = os.path.basename(target_path) src_path = os.path.join(core_src_dir, filename) @@ -45,23 +47,28 @@ def build_api_solution(source, target, env): def build(env_mono): - assert env_mono['tools'] + assert env_mono["tools"] target_filenames = [ - 'GodotSharp.dll', 'GodotSharp.pdb', 'GodotSharp.xml', - 'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml' + "GodotSharp.dll", + "GodotSharp.pdb", + "GodotSharp.xml", + "GodotSharpEditor.dll", + "GodotSharpEditor.pdb", + "GodotSharpEditor.xml", ] depend_cmd = [] - for build_config in ['Debug', 'Release']: - output_dir = Dir('#bin').abspath - editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', build_config) + for build_config in ["Debug", "Release"]: + output_dir = Dir("#bin").abspath + editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config) targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames] - cmd = env_mono.CommandNoCache(targets, depend_cmd, build_api_solution, - module_dir=os.getcwd(), solution_build_config=build_config) + cmd = env_mono.CommandNoCache( + targets, depend_cmd, build_api_solution, module_dir=os.getcwd(), solution_build_config=build_config + ) env_mono.AlwaysBuild(cmd) # Make the Release build of the API solution depend on the Debug build. diff --git a/modules/mono/build_scripts/gen_cs_glue_version.py b/modules/mono/build_scripts/gen_cs_glue_version.py index 5d1056c2fc..98bbb4d9be 100644 --- a/modules/mono/build_scripts/gen_cs_glue_version.py +++ b/modules/mono/build_scripts/gen_cs_glue_version.py @@ -1,20 +1,20 @@ - def generate_header(solution_dir, version_header_dst): import os + latest_mtime = 0 for root, dirs, files in os.walk(solution_dir, topdown=True): - dirs[:] = [d for d in dirs if d not in ['Generated']] # Ignored generated files - files = [f for f in files if f.endswith('.cs')] + dirs[:] = [d for d in dirs if d not in ["Generated"]] # Ignored generated files + files = [f for f in files if f.endswith(".cs")] for file in files: filepath = os.path.join(root, file) mtime = os.path.getmtime(filepath) latest_mtime = mtime if mtime > latest_mtime else latest_mtime - glue_version = int(latest_mtime) # The latest modified time will do for now + glue_version = int(latest_mtime) # The latest modified time will do for now - with open(version_header_dst, 'w') as version_header: - version_header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n') - version_header.write('#ifndef CS_GLUE_VERSION_H\n') - version_header.write('#define CS_GLUE_VERSION_H\n\n') - version_header.write('#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n') - version_header.write('\n#endif // CS_GLUE_VERSION_H\n') + with open(version_header_dst, "w") as version_header: + version_header.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") + version_header.write("#ifndef CS_GLUE_VERSION_H\n") + version_header.write("#define CS_GLUE_VERSION_H\n\n") + version_header.write("#define CS_GLUE_VERSION UINT32_C(" + str(glue_version) + ")\n") + version_header.write("\n#endif // CS_GLUE_VERSION_H\n") diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index 99341c631e..cffacf2577 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -8,30 +8,31 @@ from SCons.Script import Dir def build_godot_tools(source, target, env): # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - module_dir = env['module_dir'] + module_dir = env["module_dir"] - solution_path = os.path.join(module_dir, 'editor/GodotTools/GodotTools.sln') - build_config = 'Debug' if env['target'] == 'debug' else 'Release' + solution_path = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln") + build_config = "Debug" if env["target"] == "debug" else "Release" # Custom build target to make sure output is always copied to the data dir. - extra_build_args = ['/Target:Build;GodotTools:BuildAlwaysCopyToDataDir'] + extra_build_args = ["/Target:Build;GodotTools:BuildAlwaysCopyToDataDir"] + + from .solution_builder import build_solution, nuget_restore - from . solution_builder import build_solution, nuget_restore nuget_restore(env, solution_path) build_solution(env, solution_path, build_config, extra_build_args) # No need to copy targets. The GodotTools csproj takes care of copying them. def build(env_mono, api_sln_cmd): - assert env_mono['tools'] + assert env_mono["tools"] - output_dir = Dir('#bin').abspath - editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') + output_dir = Dir("#bin").abspath + editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools") - target_filenames = ['GodotTools.dll'] + target_filenames = ["GodotTools.dll"] - if env_mono['target'] == 'debug': - target_filenames += ['GodotTools.pdb'] + if env_mono["target"] == "debug": + target_filenames += ["GodotTools.pdb"] targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py index 0afd939c57..d276d7d886 100644 --- a/modules/mono/build_scripts/make_android_mono_config.py +++ b/modules/mono/build_scripts/make_android_mono_config.py @@ -1,24 +1,24 @@ - def generate_compressed_config(config_src, output_dir): import os.path - from compat import byte_to_str # Source file - with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp: - with open(config_src, 'rb') as f: + with open(os.path.join(output_dir, "android_mono_config.gen.cpp"), "w") as cpp: + with open(config_src, "rb") as f: buf = f.read() decompr_size = len(buf) import zlib + buf = zlib.compress(buf) compr_size = len(buf) - bytes_seq_str = '' + bytes_seq_str = "" for i, buf_idx in enumerate(range(compr_size)): if i > 0: - bytes_seq_str += ', ' - bytes_seq_str += byte_to_str(buf[buf_idx]) + bytes_seq_str += ", " + bytes_seq_str += str(buf[buf_idx]) - cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ + cpp.write( + """/* THIS FILE IS GENERATED DO NOT EDIT */ #include "android_mono_config.h" #ifdef ANDROID_ENABLED @@ -49,4 +49,6 @@ String get_godot_android_mono_config() { } #endif // ANDROID_ENABLED -''' % (compr_size, decompr_size, bytes_seq_str)) +""" + % (compr_size, decompr_size, bytes_seq_str) + ) diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 9a6198f13a..23f01b3cca 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -5,178 +5,230 @@ import subprocess from SCons.Script import Dir, Environment -if os.name == 'nt': +if os.name == "nt": from . import mono_reg_utils as monoreg android_arch_dirs = { - 'armv7': 'armeabi-v7a', - 'arm64v8': 'arm64-v8a', - 'x86': 'x86', - 'x86_64': 'x86_64' + "armv7": "armeabi-v7a", + "arm64v8": "arm64-v8a", + "x86": "x86", + "x86_64": "x86_64", } def get_android_out_dir(env): - return os.path.join(Dir('#platform/android/java/lib/libs').abspath, - 'release' if env['target'] == 'release' else 'debug', - android_arch_dirs[env['android_arch']]) - - -def find_file_in_dir(directory, files, prefix='', extension=''): - if not extension.startswith('.'): - extension = '.' + extension - for curfile in files: - if os.path.isfile(os.path.join(directory, prefix + curfile + extension)): - return curfile - return '' - - -def copy_file(src_dir, dst_dir, name): + return os.path.join( + Dir("#platform/android/java/lib/libs").abspath, + "release" if env["target"] == "release" else "debug", + android_arch_dirs[env["android_arch"]], + ) + + +def find_name_in_dir_files(directory, names, prefixes=[""], extensions=[""]): + for extension in extensions: + if extension and not extension.startswith("."): + extension = "." + extension + for prefix in prefixes: + for curname in names: + if os.path.isfile(os.path.join(directory, prefix + curname + extension)): + return curname + return "" + + +def find_file_in_dir(directory, names, prefixes=[""], extensions=[""]): + for extension in extensions: + if extension and not extension.startswith("."): + extension = "." + extension + for prefix in prefixes: + for curname in names: + filename = prefix + curname + extension + if os.path.isfile(os.path.join(directory, filename)): + return filename + return "" + + +def copy_file(src_dir, dst_dir, src_name, dst_name=""): from shutil import copy - src_path = os.path.join(Dir(src_dir).abspath, name) + src_path = os.path.join(Dir(src_dir).abspath, src_name) dst_dir = Dir(dst_dir).abspath if not os.path.isdir(dst_dir): os.makedirs(dst_dir) - copy(src_path, dst_dir) + if dst_name: + copy(src_path, os.path.join(dst_dir, dst_name)) + else: + copy(src_path, dst_dir) def is_desktop(platform): - return platform in ['windows', 'osx', 'x11', 'server', 'uwp', 'haiku'] + return platform in ["windows", "osx", "linuxbsd", "server", "uwp", "haiku"] def is_unix_like(platform): - return platform in ['osx', 'x11', 'server', 'android', 'haiku'] + return platform in ["osx", "linuxbsd", "server", "android", "haiku", "iphone"] def module_supports_tools_on(platform): - return platform not in ['android', 'javascript'] + return platform not in ["android", "javascript", "iphone"] def find_wasm_src_dir(mono_root): hint_dirs = [ - os.path.join(mono_root, 'src'), - os.path.join(mono_root, '../src'), + os.path.join(mono_root, "src"), + os.path.join(mono_root, "../src"), ] for hint_dir in hint_dirs: - if os.path.isfile(os.path.join(hint_dir, 'driver.c')): + if os.path.isfile(os.path.join(hint_dir, "driver.c")): return hint_dir - return '' + return "" def configure(env, env_mono): - bits = env['bits'] - is_android = env['platform'] == 'android' - is_javascript = env['platform'] == 'javascript' + bits = env["bits"] + is_android = env["platform"] == "android" + is_javascript = env["platform"] == "javascript" + is_ios = env["platform"] == "iphone" + is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"] - tools_enabled = env['tools'] - mono_static = env['mono_static'] - copy_mono_root = env['copy_mono_root'] + tools_enabled = env["tools"] + mono_static = env["mono_static"] + copy_mono_root = env["copy_mono_root"] - mono_prefix = env['mono_prefix'] + mono_prefix = env["mono_prefix"] - mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0'] + mono_lib_names = ["mono-2.0-sgen", "monosgen-2.0"] - is_travis = os.environ.get('TRAVIS') == 'true' + 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']) + env_mono.Append(CPPDEFINES=["NO_PENDING_EXCEPTIONS"]) - if is_android and not env['android_arch'] in android_arch_dirs: - raise RuntimeError('This module does not support the specified \'android_arch\': ' + env['android_arch']) + if is_android and not env["android_arch"] in android_arch_dirs: + raise RuntimeError("This module does not support the specified 'android_arch': " + env["android_arch"]) - if tools_enabled and not module_supports_tools_on(env['platform']): + if tools_enabled and not module_supports_tools_on(env["platform"]): # TODO: # Android: We have to add the data directory to the apk, concretely the Api and Tools folders. - raise RuntimeError('This module does not currently support building for this platform with tools enabled') + raise RuntimeError("This module does not currently support building for this platform with tools enabled") if is_android and mono_static: - # Android: When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0 - raise RuntimeError('Statically linking Mono is not currently supported on this platform') + # FIXME: When static linking and doing something that requires libmono-native, we get a dlopen error as 'libmono-native' + # seems to depend on 'libmonosgen-2.0'. Could be fixed by re-directing to '__Internal' with a dllmap or in the dlopen hook. + raise RuntimeError("Statically linking Mono is not currently supported for this platform") + + if not mono_static and (is_javascript or is_ios): + raise RuntimeError("Dynamically linking Mono is not currently supported for this platform") + + if not mono_prefix and (os.getenv("MONO32_PREFIX") or os.getenv("MONO64_PREFIX")): + print( + "WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead" + ) + + # Although we don't support building with tools for any platform where we currently use static AOT, + # if these are supported in the future, we won't be using static AOT for them as that would be + # too restrictive for the editor. These builds would probably be made to only use the interpreter. + mono_aot_static = (is_ios and not is_ios_sim) and not env["tools"] - if is_javascript: - mono_static = True + # Static AOT is only supported on the root domain + mono_single_appdomain = mono_aot_static - if not mono_prefix and (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')): - print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead") + if mono_single_appdomain: + env_mono.Append(CPPDEFINES=["GD_MONO_SINGLE_APPDOMAIN"]) - if env['platform'] == 'windows': + if (env["tools"] or env["target"] != "release") and not mono_single_appdomain: + env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) + + if env["platform"] == "windows": mono_root = mono_prefix - if not mono_root and os.name == 'nt': + if not mono_root and os.name == "nt": mono_root = monoreg.find_mono_root_dir(bits) if not mono_root: - raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter") + raise RuntimeError( + "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter" + ) - print('Found Mono root directory: ' + mono_root) + print("Found Mono root directory: " + mono_root) - mono_lib_path = os.path.join(mono_root, 'lib') + mono_lib_path = os.path.join(mono_root, "lib") env.Append(LIBPATH=mono_lib_path) - env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) + env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) + + lib_suffixes = [".lib"] - lib_suffix = Environment()['LIBSUFFIX'] + if not env.msvc: + # MingW supports both '.a' and '.lib' + lib_suffixes.insert(0, ".a") if mono_static: if env.msvc: - mono_static_lib_name = 'libmono-static-sgen' + mono_static_lib_name = "libmono-static-sgen" else: - mono_static_lib_name = 'libmonosgen-2.0' + mono_static_lib_name = "libmonosgen-2.0" - if not os.path.isfile(os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)): - raise RuntimeError('Could not find static mono library in: ' + mono_lib_path) + mono_static_lib_file = find_file_in_dir(mono_lib_path, [mono_static_lib_name], extensions=lib_suffixes) + + if not mono_static_lib_file: + raise RuntimeError("Could not find static mono library in: " + mono_lib_path) if env.msvc: - env.Append(LINKFLAGS=mono_static_lib_name + lib_suffix) + env.Append(LINKFLAGS=mono_static_lib_file) - env.Append(LINKFLAGS='Mincore' + lib_suffix) - env.Append(LINKFLAGS='msvcrt' + lib_suffix) - env.Append(LINKFLAGS='LIBCMT' + lib_suffix) - env.Append(LINKFLAGS='Psapi' + lib_suffix) + env.Append(LINKFLAGS="Mincore.lib") + env.Append(LINKFLAGS="msvcrt.lib") + env.Append(LINKFLAGS="LIBCMT.lib") + env.Append(LINKFLAGS="Psapi.lib") else: - env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)) + mono_static_lib_file_path = os.path.join(mono_lib_path, mono_static_lib_file) + env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_static_lib_file_path, "-Wl,-no-whole-archive"]) - env.Append(LIBS=['psapi']) - env.Append(LIBS=['version']) + env.Append(LIBS=["psapi"]) + env.Append(LIBS=["version"]) else: - mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension=lib_suffix) + mono_lib_name = find_name_in_dir_files( + mono_lib_path, mono_lib_names, prefixes=["", "lib"], extensions=lib_suffixes + ) if not mono_lib_name: - raise RuntimeError('Could not find mono library in: ' + mono_lib_path) + raise RuntimeError("Could not find mono library in: " + mono_lib_path) if env.msvc: - env.Append(LINKFLAGS=mono_lib_name + lib_suffix) + env.Append(LINKFLAGS=mono_lib_name + ".lib") else: env.Append(LIBS=[mono_lib_name]) - mono_bin_path = os.path.join(mono_root, 'bin') + mono_bin_path = os.path.join(mono_root, "bin") - mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll') + mono_dll_file = find_file_in_dir(mono_bin_path, mono_lib_names, prefixes=["", "lib"], extensions=[".dll"]) - if not mono_dll_name: - raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path) + if not mono_dll_file: + raise RuntimeError("Could not find mono shared library in: " + mono_bin_path) - copy_file(mono_bin_path, '#bin', mono_dll_name + '.dll') + copy_file(mono_bin_path, "#bin", mono_dll_file) else: - is_apple = env['platform'] in ['osx', 'iphone'] + is_apple = env["platform"] in ["osx", "iphone"] + is_macos = is_apple and not is_ios - sharedlib_ext = '.dylib' if is_apple else '.so' + sharedlib_ext = ".dylib" if is_apple else ".so" mono_root = mono_prefix - mono_lib_path = '' - mono_so_name = '' + mono_lib_path = "" + mono_so_file = "" - if not mono_root and (is_android or is_javascript): - raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter") + if not mono_root and (is_android or is_javascript or is_ios): + raise RuntimeError( + "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter" + ) - if not mono_root and is_apple: + if not mono_root and is_macos: # Try with some known directories under OSX - hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current', '/usr/local/var/homebrew/linked/mono'] + hint_dirs = ["/Library/Frameworks/Mono.framework/Versions/Current", "/usr/local/var/homebrew/linked/mono"] for hint_dir in hint_dirs: if os.path.isdir(hint_dir): mono_root = hint_dir @@ -187,126 +239,165 @@ def configure(env, env_mono): if not mono_root and mono_static: mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext) if not mono_root: - raise RuntimeError("Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " + \ - "specify one manually with the 'mono_prefix' SCons parameter") + raise RuntimeError( + "Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " + + "specify one manually with the 'mono_prefix' SCons parameter" + ) + + if is_ios and not is_ios_sim: + env_mono.Append(CPPDEFINES=["IOS_DEVICE"]) if mono_root: - print('Found Mono root directory: ' + mono_root) + print("Found Mono root directory: " + mono_root) - mono_lib_path = os.path.join(mono_root, 'lib') + mono_lib_path = os.path.join(mono_root, "lib") env.Append(LIBPATH=[mono_lib_path]) - env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) + env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) - mono_lib = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension='.a') + mono_lib = find_name_in_dir_files(mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[".a"]) if not mono_lib: - raise RuntimeError('Could not find mono library in: ' + mono_lib_path) + raise RuntimeError("Could not find mono library in: " + mono_lib_path) - env_mono.Append(CPPDEFINES=['_REENTRANT']) + env_mono.Append(CPPDEFINES=["_REENTRANT"]) if mono_static: - env.Append(LINKFLAGS=['-rdynamic']) + env.Append(LINKFLAGS=["-rdynamic"]) - mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a') + mono_lib_file = os.path.join(mono_lib_path, "lib" + mono_lib + ".a") if is_apple: - env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file]) + if is_macos: + env.Append(LINKFLAGS=["-Wl,-force_load," + mono_lib_file]) + else: + arch = env["arch"] + + def copy_mono_lib(libname_wo_ext): + copy_file( + mono_lib_path, "#bin", libname_wo_ext + ".a", "%s.iphone.%s.a" % (libname_wo_ext, arch) + ) + + # Copy Mono libraries to the output folder. These are meant to be bundled with + # the export templates and added to the Xcode project when exporting a game. + copy_mono_lib("lib" + mono_lib) + copy_mono_lib("libmono-native") + copy_mono_lib("libmono-profiler-log") + + if not is_ios_sim: + copy_mono_lib("libmono-ee-interp") + copy_mono_lib("libmono-icall-table") + copy_mono_lib("libmono-ilgen") else: - assert is_desktop(env['platform']) or is_android or is_javascript - env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive']) + assert is_desktop(env["platform"]) or is_android or is_javascript + env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_lib_file, "-Wl,-no-whole-archive"]) if is_javascript: - env.Append(LIBS=['mono-icall-table', 'mono-native', 'mono-ilgen', 'mono-ee-interp']) + env.Append(LIBS=["mono-icall-table", "mono-native", "mono-ilgen", "mono-ee-interp"]) - wasm_src_dir = os.path.join(mono_root, 'src') + wasm_src_dir = os.path.join(mono_root, "src") if not os.path.isdir(wasm_src_dir): - raise RuntimeError('Could not find mono wasm src directory') + raise RuntimeError("Could not find mono wasm src directory") # Ideally this should be defined only for 'driver.c', but I can't fight scons for another 2 hours - env_mono.Append(CPPDEFINES=['CORE_BINDINGS']) - - env_mono.add_source_files(env.modules_sources, [ - os.path.join(wasm_src_dir, 'driver.c'), - os.path.join(wasm_src_dir, 'zlib-helper.c'), - os.path.join(wasm_src_dir, 'corebindings.c') - ]) - - env.Append(LINKFLAGS=[ - '--js-library', os.path.join(wasm_src_dir, 'library_mono.js'), - '--js-library', os.path.join(wasm_src_dir, 'binding_support.js'), - '--js-library', os.path.join(wasm_src_dir, 'dotnet_support.js') - ]) + env_mono.Append(CPPDEFINES=["CORE_BINDINGS"]) + + env_mono.add_source_files( + env.modules_sources, + [ + os.path.join(wasm_src_dir, "driver.c"), + os.path.join(wasm_src_dir, "zlib-helper.c"), + os.path.join(wasm_src_dir, "corebindings.c"), + ], + ) + + env.Append( + LINKFLAGS=[ + "--js-library", + os.path.join(wasm_src_dir, "library_mono.js"), + "--js-library", + os.path.join(wasm_src_dir, "binding_support.js"), + "--js-library", + os.path.join(wasm_src_dir, "dotnet_support.js"), + ] + ) else: env.Append(LIBS=[mono_lib]) - if is_apple: - env.Append(LIBS=['iconv', 'pthread']) + if is_macos: + env.Append(LIBS=["iconv", "pthread"]) elif is_android: - pass # Nothing + pass # Nothing + elif is_ios: + pass # Nothing, linking is delegated to the exported Xcode project elif is_javascript: - env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) + env.Append(LIBS=["m", "rt", "dl", "pthread"]) else: - env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) + env.Append(LIBS=["m", "rt", "dl", "pthread"]) if not mono_static: - mono_so_name = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension=sharedlib_ext) + mono_so_file = find_file_in_dir( + mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext] + ) - if not mono_so_name: - raise RuntimeError('Could not find mono shared library in: ' + mono_lib_path) - - copy_file(mono_lib_path, '#bin', 'lib' + mono_so_name + sharedlib_ext) + if not mono_so_file: + raise RuntimeError("Could not find mono shared library in: " + mono_lib_path) else: assert not mono_static # TODO: Add option to force using pkg-config - print('Mono root directory not found. Using pkg-config instead') + print("Mono root directory not found. Using pkg-config instead") - env.ParseConfig('pkg-config monosgen-2 --libs') - env_mono.ParseConfig('pkg-config monosgen-2 --cflags') + env.ParseConfig("pkg-config monosgen-2 --libs") + env_mono.ParseConfig("pkg-config monosgen-2 --cflags") tmpenv = Environment() - tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH')) - tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L') + tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) + tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L") - for hint_dir in tmpenv['LIBPATH']: - name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext) - if name_found: + for hint_dir in tmpenv["LIBPATH"]: + file_found = find_file_in_dir(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext]) + if file_found: mono_lib_path = hint_dir - mono_so_name = name_found + mono_so_file = file_found break - if not mono_so_name: - raise RuntimeError('Could not find mono shared library in: ' + str(tmpenv['LIBPATH'])) + if not mono_so_file: + raise RuntimeError("Could not find mono shared library in: " + str(tmpenv["LIBPATH"])) if not mono_static: - libs_output_dir = get_android_out_dir(env) if is_android else '#bin' - copy_file(mono_lib_path, libs_output_dir, 'lib' + mono_so_name + sharedlib_ext) + libs_output_dir = get_android_out_dir(env) if is_android else "#bin" + copy_file(mono_lib_path, libs_output_dir, mono_so_file) if not tools_enabled: - if is_desktop(env['platform']): + if is_desktop(env["platform"]): if not mono_root: - mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() + mono_root = ( + subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip() + ) make_template_dir(env, mono_root) elif is_android: # Compress Android Mono Config from . import make_android_mono_config + module_dir = os.getcwd() - config_file_path = os.path.join(module_dir, 'build_scripts', 'mono_android_config.xml') - make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/') + config_file_path = os.path.join(module_dir, "build_scripts", "mono_android_config.xml") + make_android_mono_config.generate_compressed_config(config_file_path, "mono_gd/") # Copy the required shared libraries copy_mono_shared_libs(env, mono_root, None) elif is_javascript: - pass # No data directory for this platform + pass # No data directory for this platform + elif is_ios: + pass # No data directory for this platform if copy_mono_root: if not mono_root: - mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() + mono_root = subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip() if tools_enabled: - copy_mono_root_files(env, mono_root) + copy_mono_root_files(env, mono_root) else: print("Ignoring option: 'copy_mono_root'; only available for builds with 'tools' enabled.") @@ -314,26 +405,26 @@ def configure(env, env_mono): def make_template_dir(env, mono_root): from shutil import rmtree - platform = env['platform'] - target = env['target'] + platform = env["platform"] + target = env["target"] - template_dir_name = '' + template_dir_name = "" assert is_desktop(platform) - template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target) + template_dir_name = "data.mono.%s.%s.%s" % (platform, env["bits"], target) - output_dir = Dir('#bin').abspath + output_dir = Dir("#bin").abspath template_dir = os.path.join(output_dir, template_dir_name) - template_mono_root_dir = os.path.join(template_dir, 'Mono') + template_mono_root_dir = os.path.join(template_dir, "Mono") if os.path.isdir(template_mono_root_dir): - rmtree(template_mono_root_dir) # Clean first + rmtree(template_mono_root_dir) # Clean first # Copy etc/mono/ - template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono') + template_mono_config_dir = os.path.join(template_mono_root_dir, "etc", "mono") copy_mono_etc_dir(mono_root, template_mono_config_dir, platform) # Copy the required shared libraries @@ -347,18 +438,18 @@ def copy_mono_root_files(env, mono_root): from shutil import rmtree if not mono_root: - raise RuntimeError('Mono installation directory not found') + raise RuntimeError("Mono installation directory not found") - output_dir = Dir('#bin').abspath - editor_mono_root_dir = os.path.join(output_dir, 'GodotSharp', 'Mono') + output_dir = Dir("#bin").abspath + editor_mono_root_dir = os.path.join(output_dir, "GodotSharp", "Mono") if os.path.isdir(editor_mono_root_dir): - rmtree(editor_mono_root_dir) # Clean first + rmtree(editor_mono_root_dir) # Clean first # Copy etc/mono/ - editor_mono_config_dir = os.path.join(editor_mono_root_dir, 'etc', 'mono') - copy_mono_etc_dir(mono_root, editor_mono_config_dir, env['platform']) + editor_mono_config_dir = os.path.join(editor_mono_root_dir, "etc", "mono") + copy_mono_etc_dir(mono_root, editor_mono_config_dir, env["platform"]) # Copy the required shared libraries @@ -366,20 +457,20 @@ def copy_mono_root_files(env, mono_root): # Copy framework assemblies - mono_framework_dir = os.path.join(mono_root, 'lib', 'mono', '4.5') - mono_framework_facades_dir = os.path.join(mono_framework_dir, 'Facades') + mono_framework_dir = os.path.join(mono_root, "lib", "mono", "4.5") + mono_framework_facades_dir = os.path.join(mono_framework_dir, "Facades") - editor_mono_framework_dir = os.path.join(editor_mono_root_dir, 'lib', 'mono', '4.5') - editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, 'Facades') + editor_mono_framework_dir = os.path.join(editor_mono_root_dir, "lib", "mono", "4.5") + editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, "Facades") if not os.path.isdir(editor_mono_framework_dir): os.makedirs(editor_mono_framework_dir) if not os.path.isdir(editor_mono_framework_facades_dir): os.makedirs(editor_mono_framework_facades_dir) - for assembly in glob(os.path.join(mono_framework_dir, '*.dll')): + for assembly in glob(os.path.join(mono_framework_dir, "*.dll")): copy(assembly, editor_mono_framework_dir) - for assembly in glob(os.path.join(mono_framework_facades_dir, '*.dll')): + for assembly in glob(os.path.join(mono_framework_facades_dir, "*.dll")): copy(assembly, editor_mono_framework_facades_dir) @@ -391,28 +482,28 @@ def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform): if not os.path.isdir(target_mono_config_dir): os.makedirs(target_mono_config_dir) - mono_etc_dir = os.path.join(mono_root, 'etc', 'mono') + mono_etc_dir = os.path.join(mono_root, "etc", "mono") if not os.path.isdir(mono_etc_dir): - mono_etc_dir = '' + mono_etc_dir = "" etc_hint_dirs = [] - if platform != 'windows': - etc_hint_dirs += ['/etc/mono', '/usr/local/etc/mono'] - if 'MONO_CFG_DIR' in os.environ: - etc_hint_dirs += [os.path.join(os.environ['MONO_CFG_DIR'], 'mono')] + if platform != "windows": + etc_hint_dirs += ["/etc/mono", "/usr/local/etc/mono"] + if "MONO_CFG_DIR" in os.environ: + etc_hint_dirs += [os.path.join(os.environ["MONO_CFG_DIR"], "mono")] for etc_hint_dir in etc_hint_dirs: if os.path.isdir(etc_hint_dir): mono_etc_dir = etc_hint_dir break if not mono_etc_dir: - raise RuntimeError('Mono installation etc directory not found') + raise RuntimeError("Mono installation etc directory not found") - copy_tree(os.path.join(mono_etc_dir, '2.0'), os.path.join(target_mono_config_dir, '2.0')) - copy_tree(os.path.join(mono_etc_dir, '4.0'), os.path.join(target_mono_config_dir, '4.0')) - copy_tree(os.path.join(mono_etc_dir, '4.5'), os.path.join(target_mono_config_dir, '4.5')) - if os.path.isdir(os.path.join(mono_etc_dir, 'mconfig')): - copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig')) + copy_tree(os.path.join(mono_etc_dir, "2.0"), os.path.join(target_mono_config_dir, "2.0")) + copy_tree(os.path.join(mono_etc_dir, "4.0"), os.path.join(target_mono_config_dir, "4.0")) + copy_tree(os.path.join(mono_etc_dir, "4.5"), os.path.join(target_mono_config_dir, "4.5")) + if os.path.isdir(os.path.join(mono_etc_dir, "mconfig")): + copy_tree(os.path.join(mono_etc_dir, "mconfig"), os.path.join(target_mono_config_dir, "mconfig")) - for file in glob(os.path.join(mono_etc_dir, '*')): + for file in glob(os.path.join(mono_etc_dir, "*")): if os.path.isfile(file): copy(file, target_mono_config_dir) @@ -424,48 +515,66 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): if os.path.isfile(src): copy(src, dst) - platform = env['platform'] + platform = env["platform"] - if platform == 'windows': - src_mono_bin_dir = os.path.join(mono_root, 'bin') - target_mono_bin_dir = os.path.join(target_mono_root_dir, 'bin') + if platform == "windows": + src_mono_bin_dir = os.path.join(mono_root, "bin") + target_mono_bin_dir = os.path.join(target_mono_root_dir, "bin") if not os.path.isdir(target_mono_bin_dir): os.makedirs(target_mono_bin_dir) - mono_posix_helper_name = find_file_in_dir(src_mono_bin_dir, ['MonoPosixHelper', 'libMonoPosixHelper'], extension='.dll') - copy(os.path.join(src_mono_bin_dir, mono_posix_helper_name + '.dll'), os.path.join(target_mono_bin_dir, 'MonoPosixHelper.dll')) + mono_posix_helper_file = find_file_in_dir( + src_mono_bin_dir, ["MonoPosixHelper"], prefixes=["", "lib"], extensions=[".dll"] + ) + copy( + os.path.join(src_mono_bin_dir, mono_posix_helper_file), + os.path.join(target_mono_bin_dir, "MonoPosixHelper.dll"), + ) # For newer versions - btls_dll_path = os.path.join(src_mono_bin_dir, 'libmono-btls-shared.dll') + btls_dll_path = os.path.join(src_mono_bin_dir, "libmono-btls-shared.dll") if os.path.isfile(btls_dll_path): copy(btls_dll_path, target_mono_bin_dir) else: - target_mono_lib_dir = get_android_out_dir(env) if platform == 'android' else os.path.join(target_mono_root_dir, 'lib') + target_mono_lib_dir = ( + get_android_out_dir(env) if platform == "android" else os.path.join(target_mono_root_dir, "lib") + ) if not os.path.isdir(target_mono_lib_dir): os.makedirs(target_mono_lib_dir) lib_file_names = [] - if platform == 'osx': - lib_file_names = [lib_name + '.dylib' for lib_name in [ - 'libmono-btls-shared', 'libmono-native-compat', 'libMonoPosixHelper' - ]] + if platform == "osx": + lib_file_names = [ + lib_name + ".dylib" + for lib_name in ["libmono-btls-shared", "libmono-native-compat", "libMonoPosixHelper"] + ] elif is_unix_like(platform): - lib_file_names = [lib_name + '.so' for lib_name in [ - 'libmono-btls-shared', 'libmono-ee-interp', 'libmono-native', 'libMonoPosixHelper', - 'libmono-profiler-aot', 'libmono-profiler-coverage', 'libmono-profiler-log', 'libMonoSupportW' - ]] + lib_file_names = [ + lib_name + ".so" + for lib_name in [ + "libmono-btls-shared", + "libmono-ee-interp", + "libmono-native", + "libMonoPosixHelper", + "libmono-profiler-aot", + "libmono-profiler-coverage", + "libmono-profiler-log", + "libMonoSupportW", + ] + ] for lib_file_name in lib_file_names: - copy_if_exists(os.path.join(mono_root, 'lib', lib_file_name), target_mono_lib_dir) + copy_if_exists(os.path.join(mono_root, "lib", lib_file_name), target_mono_lib_dir) + def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): tmpenv = Environment() - tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH')) - tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L') - for hint_dir in tmpenv['LIBPATH']: - name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext) - if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')): - return os.path.join(hint_dir, '..') - return '' + tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) + tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L") + for hint_dir in tmpenv["LIBPATH"]: + name_found = find_name_in_dir_files(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext]) + if name_found and os.path.isdir(os.path.join(hint_dir, "..", "include", "mono-2.0")): + return os.path.join(hint_dir, "..") + return "" diff --git a/modules/mono/build_scripts/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py index b2c48f0a61..3090a4759a 100644 --- a/modules/mono/build_scripts/mono_reg_utils.py +++ b/modules/mono/build_scripts/mono_reg_utils.py @@ -1,21 +1,16 @@ import os import platform -from compat import decode_utf8 - -if os.name == 'nt': +if os.name == "nt": import sys - if sys.version_info < (3,): - import _winreg as winreg - else: - import winreg + import winreg def _reg_open_key(key, subkey): try: return winreg.OpenKey(key, subkey) except (WindowsError, OSError): - if platform.architecture()[0] == '32bit': + if platform.architecture()[0] == "32bit": bitness_sam = winreg.KEY_WOW64_64KEY else: bitness_sam = winreg.KEY_WOW64_32KEY @@ -25,12 +20,12 @@ def _reg_open_key(key, subkey): def _reg_open_key_bits(key, subkey, bits): sam = winreg.KEY_READ - if platform.architecture()[0] == '32bit': - if bits == '64': + if platform.architecture()[0] == "32bit": + if bits == "64": # Force 32bit process to search in 64bit registry sam |= winreg.KEY_WOW64_64KEY else: - if bits == '32': + if bits == "32": # Force 64bit process to search in 32bit registry sam |= winreg.KEY_WOW64_32KEY @@ -40,7 +35,7 @@ def _reg_open_key_bits(key, subkey, bits): def _find_mono_in_reg(subkey, bits): try: with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey: - value = winreg.QueryValueEx(hKey, 'SdkInstallRoot')[0] + value = winreg.QueryValueEx(hKey, "SdkInstallRoot")[0] return value except (WindowsError, OSError): return None @@ -49,70 +44,70 @@ def _find_mono_in_reg(subkey, bits): def _find_mono_in_reg_old(subkey, bits): try: with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey: - default_clr = winreg.QueryValueEx(hKey, 'DefaultCLR')[0] + default_clr = winreg.QueryValueEx(hKey, "DefaultCLR")[0] if default_clr: - return _find_mono_in_reg(subkey + '\\' + default_clr, bits) + return _find_mono_in_reg(subkey + "\\" + default_clr, bits) return None except (WindowsError, EnvironmentError): return None def find_mono_root_dir(bits): - root_dir = _find_mono_in_reg(r'SOFTWARE\Mono', bits) + root_dir = _find_mono_in_reg(r"SOFTWARE\Mono", bits) if root_dir is not None: return str(root_dir) - root_dir = _find_mono_in_reg_old(r'SOFTWARE\Novell\Mono', bits) + root_dir = _find_mono_in_reg_old(r"SOFTWARE\Novell\Mono", bits) if root_dir is not None: return str(root_dir) - return '' + return "" def find_msbuild_tools_path_reg(): import subprocess - vswhere = os.getenv('PROGRAMFILES(X86)') + vswhere = os.getenv("PROGRAMFILES(X86)") if not vswhere: - vswhere = os.getenv('PROGRAMFILES') - vswhere += r'\Microsoft Visual Studio\Installer\vswhere.exe' + vswhere = os.getenv("PROGRAMFILES") + vswhere += r"\Microsoft Visual Studio\Installer\vswhere.exe" - vswhere_args = ['-latest', '-products', '*', '-requires', 'Microsoft.Component.MSBuild'] + vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"] try: lines = subprocess.check_output([vswhere] + vswhere_args).splitlines() for line in lines: - parts = decode_utf8(line).split(':', 1) + parts = line.decode("utf-8").split(":", 1) - if len(parts) < 2 or parts[0] != 'installationPath': + if len(parts) < 2 or parts[0] != "installationPath": continue val = parts[1].strip() if not val: - raise ValueError('Value of `installationPath` entry is empty') + raise ValueError("Value of `installationPath` entry is empty") # Since VS2019, the directory is simply named "Current" - msbuild_dir = os.path.join(val, 'MSBuild\\Current\\Bin') + msbuild_dir = os.path.join(val, "MSBuild\\Current\\Bin") if os.path.isdir(msbuild_dir): return msbuild_dir # Directory name "15.0" is used in VS 2017 - return os.path.join(val, 'MSBuild\\15.0\\Bin') + return os.path.join(val, "MSBuild\\15.0\\Bin") - raise ValueError('Cannot find `installationPath` entry') + raise ValueError("Cannot find `installationPath` entry") except ValueError as e: - print('Error reading output from vswhere: ' + e.message) + print("Error reading output from vswhere: " + e.message) except WindowsError: - pass # Fine, vswhere not found + pass # Fine, vswhere not found except (subprocess.CalledProcessError, OSError): pass # Try to find 14.0 in the Registry try: - subkey = r'SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0' + subkey = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0" with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey: - value = winreg.QueryValueEx(hKey, 'MSBuildToolsPath')[0] + value = winreg.QueryValueEx(hKey, "MSBuildToolsPath")[0] return value except (WindowsError, OSError): - return '' + return "" diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py index d1529a64d2..db6b4ff7aa 100644 --- a/modules/mono/build_scripts/solution_builder.py +++ b/modules/mono/build_scripts/solution_builder.py @@ -1,4 +1,3 @@ - import os @@ -8,35 +7,38 @@ verbose = False def find_nuget_unix(): import os - if 'NUGET_PATH' in os.environ: - hint_path = os.environ['NUGET_PATH'] + if "NUGET_PATH" in os.environ: + hint_path = os.environ["NUGET_PATH"] if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path - hint_path = os.path.join(hint_path, 'nuget') + hint_path = os.path.join(hint_path, "nuget") if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path import os.path import sys - hint_dirs = ['/opt/novell/mono/bin'] - if sys.platform == 'darwin': - hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs + hint_dirs = ["/opt/novell/mono/bin"] + if sys.platform == "darwin": + hint_dirs = [ + "/Library/Frameworks/Mono.framework/Versions/Current/bin", + "/usr/local/var/homebrew/linked/mono/bin", + ] + hint_dirs for hint_dir in hint_dirs: - hint_path = os.path.join(hint_dir, 'nuget') + hint_path = os.path.join(hint_dir, "nuget") if os.path.isfile(hint_path): return hint_path - elif os.path.isfile(hint_path + '.exe'): - return hint_path + '.exe' + elif os.path.isfile(hint_path + ".exe"): + return hint_path + ".exe" - for hint_dir in os.environ['PATH'].split(os.pathsep): + for hint_dir in os.environ["PATH"].split(os.pathsep): hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, 'nuget') + hint_path = os.path.join(hint_dir, "nuget") if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path - if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK): - return hint_path + '.exe' + if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): + return hint_path + ".exe" return None @@ -44,30 +46,30 @@ def find_nuget_unix(): def find_nuget_windows(env): import os - if 'NUGET_PATH' in os.environ: - hint_path = os.environ['NUGET_PATH'] + if "NUGET_PATH" in os.environ: + hint_path = os.environ["NUGET_PATH"] if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path - hint_path = os.path.join(hint_path, 'nuget.exe') + hint_path = os.path.join(hint_path, "nuget.exe") if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path - from . mono_reg_utils import find_mono_root_dir + from .mono_reg_utils import find_mono_root_dir - mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits']) + mono_root = env["mono_prefix"] or find_mono_root_dir(env["bits"]) if mono_root: - mono_bin_dir = os.path.join(mono_root, 'bin') - nuget_mono = os.path.join(mono_bin_dir, 'nuget.bat') + mono_bin_dir = os.path.join(mono_root, "bin") + nuget_mono = os.path.join(mono_bin_dir, "nuget.bat") if os.path.isfile(nuget_mono): return nuget_mono # Standalone NuGet - for hint_dir in os.environ['PATH'].split(os.pathsep): + for hint_dir in os.environ["PATH"].split(os.pathsep): hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, 'nuget.exe') + hint_path = os.path.join(hint_dir, "nuget.exe") if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path @@ -78,52 +80,55 @@ def find_msbuild_unix(filename): import os.path import sys - hint_dirs = ['/opt/novell/mono/bin'] - if sys.platform == 'darwin': - hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs + hint_dirs = ["/opt/novell/mono/bin"] + if sys.platform == "darwin": + hint_dirs = [ + "/Library/Frameworks/Mono.framework/Versions/Current/bin", + "/usr/local/var/homebrew/linked/mono/bin", + ] + hint_dirs for hint_dir in hint_dirs: hint_path = os.path.join(hint_dir, filename) if os.path.isfile(hint_path): return hint_path - elif os.path.isfile(hint_path + '.exe'): - return hint_path + '.exe' + elif os.path.isfile(hint_path + ".exe"): + return hint_path + ".exe" - for hint_dir in os.environ['PATH'].split(os.pathsep): + for hint_dir in os.environ["PATH"].split(os.pathsep): hint_dir = hint_dir.strip('"') hint_path = os.path.join(hint_dir, filename) if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): return hint_path - if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK): - return hint_path + '.exe' + if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): + return hint_path + ".exe" return None def find_msbuild_windows(env): - from . mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg + from .mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg - mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits']) + mono_root = env["mono_prefix"] or find_mono_root_dir(env["bits"]) if not mono_root: - raise RuntimeError('Cannot find mono root directory') + raise RuntimeError("Cannot find mono root directory") - mono_bin_dir = os.path.join(mono_root, 'bin') - msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat') + mono_bin_dir = os.path.join(mono_root, "bin") + msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat") msbuild_tools_path = find_msbuild_tools_path_reg() if msbuild_tools_path: - return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), {}) + return (os.path.join(msbuild_tools_path, "MSBuild.exe"), {}) if os.path.isfile(msbuild_mono): # The (Csc/Vbc/Fsc)ToolExe environment variables are required when # building with Mono's MSBuild. They must point to the batch files # in Mono's bin directory to make sure they are executed with Mono. mono_msbuild_env = { - 'CscToolExe': os.path.join(mono_bin_dir, 'csc.bat'), - 'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'), - 'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat') + "CscToolExe": os.path.join(mono_bin_dir, "csc.bat"), + "VbcToolExe": os.path.join(mono_bin_dir, "vbc.bat"), + "FscToolExe": os.path.join(mono_bin_dir, "fsharpc.bat"), } return (msbuild_mono, mono_msbuild_env) @@ -132,7 +137,7 @@ def find_msbuild_windows(env): def run_command(command, args, env_override=None, name=None): def cmd_args_to_str(cmd_args): - return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args]) + return " ".join([arg if not " " in arg else '"%s"' % arg for arg in cmd_args]) args = [command] + args @@ -143,6 +148,7 @@ def run_command(command, args, env_override=None, name=None): print("Running '%s': %s" % (name, cmd_args_to_str(args))) import subprocess + try: if env_override is None: subprocess.check_call(args) @@ -154,61 +160,61 @@ def run_command(command, args, env_override=None, name=None): def nuget_restore(env, *args): global verbose - verbose = env['verbose'] + verbose = env["verbose"] # Find NuGet - nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix() + 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') + raise RuntimeError("Cannot find NuGet executable") - print('NuGet path: ' + nuget_path) + print("NuGet path: " + nuget_path) # Do NuGet restore - run_command(nuget_path, ['restore'] + list(args), name='nuget restore') + run_command(nuget_path, ["restore"] + list(args), name="nuget restore") def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): global verbose - verbose = env['verbose'] + verbose = env["verbose"] msbuild_env = os.environ.copy() # Needed when running from Developer Command Prompt for VS - if 'PLATFORM' in msbuild_env: - del msbuild_env['PLATFORM'] + if "PLATFORM" in msbuild_env: + del msbuild_env["PLATFORM"] # Find MSBuild - if os.name == 'nt': + if os.name == "nt": msbuild_info = find_msbuild_windows(env) if msbuild_info is None: - raise RuntimeError('Cannot find MSBuild executable') + raise RuntimeError("Cannot find MSBuild executable") msbuild_path = msbuild_info[0] msbuild_env.update(msbuild_info[1]) else: - msbuild_path = find_msbuild_unix('msbuild') + msbuild_path = find_msbuild_unix("msbuild") if msbuild_path is None: - xbuild_fallback = env['xbuild_fallback'] + xbuild_fallback = env["xbuild_fallback"] - if xbuild_fallback and os.name == 'nt': - print('Option \'xbuild_fallback\' not supported on Windows') + if xbuild_fallback and os.name == "nt": + print("Option 'xbuild_fallback' not supported on Windows") xbuild_fallback = False if xbuild_fallback: - print('Cannot find MSBuild executable, trying with xbuild') - print('Warning: xbuild is deprecated') + print("Cannot find MSBuild executable, trying with xbuild") + print("Warning: xbuild is deprecated") - msbuild_path = find_msbuild_unix('xbuild') + msbuild_path = find_msbuild_unix("xbuild") if msbuild_path is None: - raise RuntimeError('Cannot find xbuild executable') + raise RuntimeError("Cannot find xbuild executable") else: - raise RuntimeError('Cannot find MSBuild executable') + raise RuntimeError("Cannot find MSBuild executable") - print('MSBuild path: ' + msbuild_path) + print("MSBuild path: " + msbuild_path) # Build solution - msbuild_args = [solution_path, '/p:Configuration=' + build_config] + msbuild_args = [solution_path, "/p:Configuration=" + build_config] msbuild_args += extra_msbuild_args - run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild') + run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild") diff --git a/modules/mono/build_scripts/tls_configure.py b/modules/mono/build_scripts/tls_configure.py deleted file mode 100644 index 622280b00b..0000000000 --- a/modules/mono/build_scripts/tls_configure.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import print_function - -def supported(result): - return 'supported' if result else 'not supported' - - -def check_cxx11_thread_local(conf): - print('Checking for `thread_local` support...', end=" ") - result = conf.TryCompile('thread_local int foo = 0; int main() { return foo; }', '.cpp') - print(supported(result)) - return bool(result) - - -def check_declspec_thread(conf): - print('Checking for `__declspec(thread)` support...', end=" ") - result = conf.TryCompile('__declspec(thread) int foo = 0; int main() { return foo; }', '.cpp') - print(supported(result)) - return bool(result) - - -def check_gcc___thread(conf): - print('Checking for `__thread` support...', end=" ") - result = conf.TryCompile('__thread int foo = 0; int main() { return foo; }', '.cpp') - print(supported(result)) - return bool(result) - - -def configure(conf): - if check_cxx11_thread_local(conf): - conf.env.Append(CPPDEFINES=['HAVE_CXX11_THREAD_LOCAL']) - else: - if conf.env.msvc: - if check_declspec_thread(conf): - conf.env.Append(CPPDEFINES=['HAVE_DECLSPEC_THREAD']) - elif check_gcc___thread(conf): - conf.env.Append(CPPDEFINES=['HAVE_GCC___THREAD']) diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp index b04e53bd81..384685d04b 100644 --- a/modules/mono/class_db_api_json.cpp +++ b/modules/mono/class_db_api_json.cpp @@ -42,7 +42,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { List<StringName> names; - const StringName *k = NULL; + const StringName *k = nullptr; while ((k = ClassDB::classes.next(k))) { @@ -67,7 +67,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { List<StringName> snames; - k = NULL; + k = nullptr; while ((k = t->method_map.next(k))) { @@ -132,7 +132,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { List<StringName> snames; - k = NULL; + k = nullptr; while ((k = t->constant_map.next(k))) { @@ -160,7 +160,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { List<StringName> snames; - k = NULL; + k = nullptr; while ((k = t->signal_map.next(k))) { @@ -196,7 +196,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { List<StringName> snames; - k = NULL; + k = nullptr; while ((k = t->property_setget.next(k))) { diff --git a/modules/mono/config.py b/modules/mono/config.py index 70cb464c7a..106ca6e028 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,42 +1,70 @@ +supported_platforms = ["windows", "osx", "linuxbsd", "server", "android", "haiku", "javascript", "iphone"] + + def can_build(env, platform): return True def configure(env): - if env['platform'] not in ['windows', 'osx', 'x11', 'server', 'android', 'haiku', 'javascript']: - raise RuntimeError('This module does not currently support building for this platform') + platform = env["platform"] + + if platform not in supported_platforms: + raise RuntimeError("This module does not currently support building for this platform") env.use_ptrcall = True - env.add_module_version_string('mono') + env.add_module_version_string("mono") - from SCons.Script import BoolVariable, PathVariable, Variables + from SCons.Script import BoolVariable, PathVariable, Variables, Help + + default_mono_static = platform in ["iphone", "javascript"] + default_mono_bundles_zlib = platform in ["javascript"] envvars = Variables() - envvars.Add(PathVariable('mono_prefix', 'Path to the mono installation directory for the target platform and architecture', '', PathVariable.PathAccept)) - envvars.Add(BoolVariable('mono_static', 'Statically link mono', False)) - envvars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True)) - envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False)) - envvars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False)) + envvars.Add( + PathVariable( + "mono_prefix", + "Path to the mono installation directory for the target platform and architecture", + "", + PathVariable.PathAccept, + ) + ) + envvars.Add(BoolVariable("mono_static", "Statically link mono", default_mono_static)) + 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)) + + # TODO: It would be great if this could be detected automatically instead + envvars.Add( + BoolVariable( + "mono_bundles_zlib", "Specify if the Mono runtime was built with bundled zlib", default_mono_bundles_zlib + ) + ) + envvars.Update(env) + Help(envvars.GenerateHelpText(env)) - if env['platform'] == 'javascript': - # Mono wasm already has zlib builtin, so we need this workaround to avoid symbol collisions - print('Compiling with Mono wasm disables \'builtin_zlib\'') - env['builtin_zlib'] = False + if env["mono_bundles_zlib"]: + # Mono may come with zlib bundled for WASM or on newer version when built with MinGW. + print("This Mono runtime comes with zlib bundled. Disabling 'builtin_zlib'...") + env["builtin_zlib"] = False thirdparty_zlib_dir = "#thirdparty/zlib/" env.Prepend(CPPPATH=[thirdparty_zlib_dir]) def get_doc_classes(): return [ - '@C#', - 'CSharpScript', - 'GodotSharp', + "@C#", + "CSharpScript", + "GodotSharp", ] def get_doc_path(): - return 'doc_classes' + return "doc_classes" def is_enabled(): diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 6809cbdff9..0b5d3c8dbc 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -33,6 +33,8 @@ #include <mono/metadata/threads.h> #include <stdint.h> +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/io/json.h" #include "core/os/file_access.h" #include "core/os/mutex.h" @@ -60,7 +62,6 @@ #include "signal_awaiter_utils.h" #include "utils/macros.h" #include "utils/string_utils.h" -#include "utils/thread_local.h" #define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) @@ -73,7 +74,7 @@ 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(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL); + CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr); return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution"); } @@ -81,7 +82,7 @@ static bool _create_project_solution_if_needed() { } #endif -CSharpLanguage *CSharpLanguage::singleton = NULL; +CSharpLanguage *CSharpLanguage::singleton = nullptr; String CSharpLanguage::get_name() const { @@ -140,21 +141,24 @@ void CSharpLanguage::init() { void CSharpLanguage::finish() { + if (finalized) + return; + finalizing = true; // 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(); - if (script_binding.gchandle.is_valid()) { - script_binding.gchandle->release(); + if (!script_binding.gchandle.is_released()) { + script_binding.gchandle.release(); script_binding.inited = false; } } if (gdmono) { memdelete(gdmono); - gdmono = NULL; + gdmono = nullptr; } // Clear here, after finalizing all domains to make sure there is nothing else referencing the elements. @@ -173,7 +177,10 @@ void CSharpLanguage::finish() { } #endif + memdelete(managed_callable_middleman); + finalizing = false; + finalized = true; } void CSharpLanguage::get_reserved_words(List<String> *p_words) const { @@ -309,7 +316,8 @@ void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("' '"); // character literal p_delimiters->push_back("\" \""); // regular string literal - p_delimiters->push_back("@\" \""); // verbatim string literal + // Verbatim string literals (`@" "`) don't render correctly, so don't highlight them. + // Generic string highlighting suffices as a workaround for now. } static String get_base_class_name(const String &p_base_class_name, const String p_class_name) { @@ -432,13 +440,12 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { return "byte[]"; if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) return "int[]"; - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) { -#ifdef REAL_T_IS_DOUBLE - return "double[]"; -#else + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) + return "long[]"; + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) return "float[]"; -#endif - } + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) + return "double[]"; if (p_var_type_name == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) return "string[]"; if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) @@ -448,12 +455,18 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { if (p_var_type_name == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) return "Color[]"; + if (p_var_type_name == Variant::get_type_name(Variant::SIGNAL)) + return "SignalInfo"; + Variant::Type var_types[] = { Variant::BOOL, Variant::INT, Variant::VECTOR2, + Variant::VECTOR2I, Variant::RECT2, + Variant::RECT2I, Variant::VECTOR3, + Variant::VECTOR3I, Variant::TRANSFORM2D, Variant::PLANE, Variant::QUAT, @@ -461,8 +474,10 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { Variant::BASIS, Variant::TRANSFORM, Variant::COLOR, + Variant::STRING_NAME, Variant::NODE_PATH, - Variant::_RID + Variant::_RID, + Variant::CALLABLE }; for (unsigned int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) { @@ -559,7 +574,13 @@ String CSharpLanguage::debug_get_stack_level_source(int p_level) const { Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() { #ifdef DEBUG_ENABLED - _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); + // Printing an error here will result in endless recursion, so we must be careful + static thread_local bool _recursion_flag_ = false; + if (_recursion_flag_) + return Vector<StackInfo>(); + _recursion_flag_ = true; + SCOPE_EXIT { _recursion_flag_ = false; }; + GD_MONO_SCOPE_THREAD_ATTACH; if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) @@ -584,10 +605,16 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { - _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); + // Printing an error here will result in endless recursion, so we must be careful + static thread_local bool _recursion_flag_ = false; + if (_recursion_flag_) + return Vector<StackInfo>(); + _recursion_flag_ = true; + SCOPE_EXIT { _recursion_flag_ = false; }; + GD_MONO_SCOPE_THREAD_ATTACH; - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoArray *frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames).invoke(p_stack_trace, &exc); @@ -652,14 +679,14 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { void CSharpLanguage::frame() { - if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) { - const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle; + if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != nullptr) { + const Ref<MonoGCHandleRef> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle; if (task_scheduler_handle.is_valid()) { MonoObject *task_scheduler = task_scheduler_handle->get_target(); if (task_scheduler) { - MonoException *exc = NULL; + MonoException *exc = nullptr; CACHED_METHOD_THUNK(GodotTaskScheduler, Activate).invoke(task_scheduler, &exc); if (exc) { @@ -737,7 +764,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { if (proj_assembly) { String proj_asm_path = proj_assembly->get_path(); - if (!FileAccess::exists(proj_assembly->get_path())) { + if (!FileAccess::exists(proj_asm_path)) { // Maybe it wasn't loaded from the default path, so check this as well proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe); if (!FileAccess::exists(proj_asm_path)) @@ -761,7 +788,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // There is no soft reloading with Mono. It's always hard reloading. - List<Ref<CSharpScript> > scripts; + List<Ref<CSharpScript>> scripts; { MutexLock lock(script_instances_mutex); @@ -772,10 +799,40 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } } - List<Ref<CSharpScript> > to_reload; + scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order + + // Serialize managed callables + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { + ManagedCallable *managed_callable = elem->self(); + + MonoDelegate *delegate = (MonoDelegate *)managed_callable->delegate_handle.get_target(); + + Array serialized_data; + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate, managed_serialized_data, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + + if (success) { + ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to serialize delegate\n"); + } + } + } + + List<Ref<CSharpScript>> to_reload; // We need to keep reference instances alive during reloading - List<Ref<Reference> > ref_instances; + List<Ref<Reference>> ref_instances; for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); @@ -787,9 +844,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // As scripts are going to be reloaded, must proceed without locking here - scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order - - for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = scripts.front(); E; E = E->next()) { Ref<CSharpScript> &script = E->get(); to_reload.push_back(script); @@ -843,13 +898,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // TODO: Proper state backup (Not only variants, serialize managed state of scripts) csi->get_properties_state_for_reloading(state.properties); + csi->get_event_signals_state_for_reloading(state.event_signals); owners_map[obj->get_instance_id()] = state; } } // After the state of all instances is saved, clear scripts and script instances - for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = scripts.front(); E; E = E->next()) { Ref<CSharpScript> &script = E->get(); while (script->instances.front()) { @@ -864,7 +920,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { if (gdmono->reload_scripts_domain() != OK) { // Failed to reload the scripts domain // Make sure to add the scripts back to their owners before returning - for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = to_reload.front(); E; E = E->next()) { Ref<CSharpScript> scr = E->get(); for (const Map<ObjectID, CSharpScript::StateBackup>::Element *F = scr->pending_reload_state.front(); F; F = F->next()) { @@ -889,8 +945,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif // Restore Variant properties state, it will be kept by the placeholder until the next script reloading - for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) { - placeholder->property_set_fallback(G->get().first, G->get().second, NULL); + for (List<Pair<StringName, Variant>>::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) { + placeholder->property_set_fallback(G->get().first, G->get().second, nullptr); } scr->pending_reload_state.erase(obj_id); @@ -900,9 +956,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } - List<Ref<CSharpScript> > to_reload_state; + List<Ref<CSharpScript>> to_reload_state; - for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = to_reload.front(); E; E = E->next()) { Ref<CSharpScript> script = E->get(); if (!script->get_path().empty()) { @@ -924,12 +980,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { GDMonoAssembly *project_assembly = gdmono->get_project_assembly(); // Search in project and tools assemblies first as those are the most likely to have the class - GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL); + GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : nullptr); #ifdef TOOLS_ENABLED if (!script_class) { GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly(); - script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL); + script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : nullptr); } #endif @@ -955,7 +1011,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpScript::initialize_for_managed_type(script, script_class, native); } - String native_name = NATIVE_GDMONOCLASS_NAME(script->native); + StringName native_name = NATIVE_GDMONOCLASS_NAME(script->native); { for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { @@ -1000,7 +1056,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { continue; } #else - CRASH_COND(si != NULL); + CRASH_COND(si != nullptr); #endif // Re-create script instance obj->set_script(script); // will create the script instance as well @@ -1010,7 +1066,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { to_reload_state.push_back(script); } - for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = to_reload_state.front(); E; E = E->next()) { Ref<CSharpScript> script = E->get(); for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { @@ -1028,19 +1084,84 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; - for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) { + for (List<Pair<StringName, Variant>>::Element *G = state_backup.properties.front(); G; G = G->next()) { obj->get_script_instance()->set(G->get().first, G->get().second); } - // Call OnAfterDeserialization CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); - if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) - obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize); + + if (csi) { + for (List<Pair<StringName, Array>>::Element *G = state_backup.event_signals.front(); G; G = G->next()) { + const StringName &name = G->get().first; + const Array &serialized_data = G->get().second; + + Map<StringName, CSharpScript::EventSignal>::Element *match = script->event_signals.find(name); + + if (!match) { + // The event or its signal attribute were removed + continue; + } + + const CSharpScript::EventSignal &event_signal = match->value(); + + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + MonoDelegate *delegate = nullptr; + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + + if (success) { + ERR_CONTINUE(delegate == nullptr); + event_signal.field->set_value(csi->get_mono_object(), (MonoObject *)delegate); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to deserialize event signal delegate\n"); + } + } + + // Call OnAfterDeserialization + if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) + obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize); + } } script->pending_reload_instances.clear(); } + // Deserialize managed callables + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (Map<ManagedCallable *, Array>::Element *elem = ManagedCallable::instances_pending_reload.front(); elem; elem = elem->next()) { + ManagedCallable *managed_callable = elem->key(); + const Array &serialized_data = elem->value(); + + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + MonoDelegate *delegate = nullptr; + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + + if (success) { + ERR_CONTINUE(delegate == nullptr); + managed_callable->set_delegate(delegate); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to deserialize delegate\n"); + } + } + + ManagedCallable::instances_pending_reload.clear(); + } + #ifdef TOOLS_ENABLED // FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative. if (Engine::get_singleton()->is_editor_hint()) { @@ -1134,11 +1255,11 @@ void CSharpLanguage::thread_exit() { bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { // Not a parser error in our case, but it's still used for other type of errors - if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) { + if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false, true); + EngineDebugger::get_script_debugger()->debug(this, false, true); return true; } else { return false; @@ -1147,11 +1268,11 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { - if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) { + if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, p_allow_continue); + EngineDebugger::get_script_debugger()->debug(this, p_allow_continue); return true; } else { return false; @@ -1161,9 +1282,20 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { void CSharpLanguage::_on_scripts_domain_unloaded() { for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); + script_binding.gchandle.release(); script_binding.inited = false; } + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { + ManagedCallable *managed_callable = elem->self(); + managed_callable->delegate_handle.release(); + managed_callable->delegate_invoke = nullptr; + } + } + scripts_metadata_invalidated = true; } @@ -1175,17 +1307,17 @@ void CSharpLanguage::_editor_init_callback() { // Initialize GodotSharpEditor GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor"); - CRASH_COND(editor_klass == NULL); + CRASH_COND(editor_klass == nullptr); MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr()); - CRASH_COND(mono_object == NULL); + CRASH_COND(mono_object == nullptr); - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc); UNHANDLED_EXCEPTION(exc); EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object)); - CRASH_COND(godotsharp_editor == NULL); + CRASH_COND(godotsharp_editor == nullptr); // Enable it as a plugin EditorNode::add_editor_plugin(godotsharp_editor); @@ -1201,57 +1333,45 @@ void CSharpLanguage::set_language_index(int p_idx) { lang_idx = p_idx; } -void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) { +void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) { - if (!p_gchandle->is_released()) { // Do not lock unnecessarily + if (!p_gchandle.is_released()) { // Do not lock unnecessarily MutexLock lock(get_singleton()->script_gchandle_release_mutex); - p_gchandle->release(); + p_gchandle.release(); } } -void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle) { +void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle) { - uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it + uint32_t pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(p_expected_obj); // We might lock after this, so pin it - if (!p_gchandle->is_released()) { // Do not lock unnecessarily + if (!p_gchandle.is_released()) { // Do not lock unnecessarily MutexLock lock(get_singleton()->script_gchandle_release_mutex); - MonoObject *target = p_gchandle->get_target(); + MonoObject *target = p_gchandle.get_target(); // We release the gchandle if it points to the MonoObject* we expect (otherwise it was // already released and could have been replaced) or if we can't get its target MonoObject* // (which doesn't necessarily mean it was released, and we want it released in order to // avoid locking other threads unnecessarily). - if (target == p_expected_obj || target == NULL) { - p_gchandle->release(); + if (target == p_expected_obj || target == nullptr) { + p_gchandle.release(); } } - MonoGCHandle::free_handle(pinned_gchandle); + GDMonoUtils::free_gchandle(pinned_gchandle); } CSharpLanguage::CSharpLanguage() { ERR_FAIL_COND_MSG(singleton, "C# singleton already exist."); singleton = this; - - finalizing = false; - - gdmono = NULL; - - lang_idx = -1; - - scripts_metadata_invalidated = true; - -#ifdef TOOLS_ENABLED - godotsharp_editor = NULL; -#endif } CSharpLanguage::~CSharpLanguage() { finish(); - singleton = NULL; + singleton = nullptr; } bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object) { @@ -1260,7 +1380,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b // I don't trust you if (p_object->get_script_instance()) { CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); - CRASH_COND(csharp_instance != NULL && !csharp_instance->is_destructing_script_instance()); + CRASH_COND(csharp_instance != nullptr && !csharp_instance->is_destructing_script_instance()); } #endif @@ -1284,7 +1404,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b r_script_binding.inited = true; r_script_binding.type_name = type_name; r_script_binding.wrapper_class = type_class; // cache - r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object); + r_script_binding.gchandle = MonoGCHandleData::new_strong_handle(mono_object); r_script_binding.owner = p_object; // Tie managed to unmanaged @@ -1314,7 +1434,7 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { CSharpScriptBinding script_binding; if (!setup_csharp_script_binding(script_binding, p_object)) - return NULL; + return nullptr; return (void *)insert_script_binding(p_object, script_binding); } @@ -1326,7 +1446,7 @@ Map<Object *, CSharpScriptBinding>::Element *CSharpLanguage::insert_script_bindi void CSharpLanguage::free_instance_binding_data(void *p_data) { - if (GDMono::get_singleton() == NULL) { + if (GDMono::get_singleton() == nullptr) { #ifdef DEBUG_ENABLED CRASH_COND(!script_bindings.empty()); #endif @@ -1349,10 +1469,11 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { if (script_binding.inited) { // Set the native instance field to IntPtr.Zero, if not yet garbage collected. // This is done to avoid trying to dispose the native instance from Dispose(bool). - MonoObject *mono_object = script_binding.gchandle->get_target(); + MonoObject *mono_object = script_binding.gchandle.get_target(); if (mono_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); + CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, nullptr); } + script_binding.gchandle.release(); } script_bindings.erase(data); @@ -1372,26 +1493,26 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { CRASH_COND(!data); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + MonoGCHandleData &gchandle = script_binding.gchandle; if (!script_binding.inited) return; - if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (ref_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. - MonoObject *target = gchandle->get_target(); + MonoObject *target = gchandle.get_target(); if (!target) return; // Called after the managed side was collected, so nothing to do here // Release the current weak handle and replace it with a strong handle. - uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target); - gchandle->release(); - gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); + MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target); + gchandle.release(); + gchandle = strong_gchandle; } } @@ -1408,27 +1529,27 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { CRASH_COND(!data); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + MonoGCHandleData &gchandle = script_binding.gchandle; int refcount = ref_owner->reference_get_count(); if (!script_binding.inited) return refcount == 0; - if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (refcount == 1 && !gchandle.is_released() && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. - MonoObject *target = gchandle->get_target(); + MonoObject *target = gchandle.get_target(); if (!target) return refcount == 0; // Called after the managed side was collected, so nothing to do here // Release the current strong handle and replace it with a weak handle. - uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target); - gchandle->release(); - gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); + MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target); + gchandle.release(); + gchandle = weak_gchandle; return false; } @@ -1436,14 +1557,13 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { return refcount == 0; } -CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle) { +CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { - CSharpInstance *instance = memnew(CSharpInstance); + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); Reference *ref = Object::cast_to<Reference>(p_owner); - instance->base_ref = ref != NULL; - instance->script = Ref<CSharpScript>(p_script); + instance->base_ref = ref != nullptr; instance->owner = p_owner; instance->gchandle = p_gchandle; @@ -1457,8 +1577,8 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS MonoObject *CSharpInstance::get_mono_object() const { - ERR_FAIL_COND_V(gchandle.is_null(), NULL); - return gchandle->get_target(); + ERR_FAIL_COND_V(gchandle.is_released(), nullptr); + return gchandle.get_target(); } Object *CSharpInstance::get_owner() { @@ -1542,7 +1662,7 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { GDMonoProperty *property = top->get_property(p_name); if (property) { - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoObject *value = property->get_value(mono_object, &exc); if (exc) { r_ret = Variant(); @@ -1583,7 +1703,7 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { return false; } -void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) { +void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) { List<PropertyInfo> pinfo; get_property_list(&pinfo); @@ -1608,6 +1728,37 @@ void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Va } } +void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state) { + + MonoObject *owner_managed = get_mono_object(); + ERR_FAIL_NULL(owner_managed); + + for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) { + const CSharpScript::EventSignal &event_signal = E->value(); + + MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed); + if (!delegate_field_value) + continue; // Empty + + Array serialized_data; + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate_field_value, managed_serialized_data, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + + if (success) { + r_state.push_back(Pair<StringName, Array>(event_signal.field->get_name(), serialized_data)); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to serialize event signal delegate\n"); + } + } +} + void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) { @@ -1759,7 +1910,7 @@ bool CSharpInstance::_reference_owner_unsafe() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); CRASH_COND(unsafe_referenced); // already referenced #endif @@ -1781,7 +1932,7 @@ bool CSharpInstance::_unreference_owner_unsafe() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); #endif if (!unsafe_referenced) @@ -1800,19 +1951,15 @@ bool CSharpInstance::_unreference_owner_unsafe() { } MonoObject *CSharpInstance::_internal_new_managed() { -#ifdef DEBUG_ENABLED - CRASH_COND(!gchandle.is_valid()); -#endif - // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - ERR_FAIL_NULL_V_MSG(ctor, NULL, + ERR_FAIL_NULL_V_MSG(ctor, nullptr, "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'."); CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); - ERR_FAIL_NULL_V(owner, NULL); - ERR_FAIL_COND_V(script.is_null(), NULL); + ERR_FAIL_NULL_V(owner, nullptr); + ERR_FAIL_COND_V(script.is_null(), nullptr); MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr()); @@ -1824,13 +1971,13 @@ MonoObject *CSharpInstance::_internal_new_managed() { // 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; + owner = nullptr; - ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); + ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); } // Tie managed to unmanaged - gchandle = MonoGCHandle::create_strong(mono_object); + gchandle = MonoGCHandleData::new_strong_handle(mono_object); if (base_ref) _reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) @@ -1838,16 +1985,18 @@ MonoObject *CSharpInstance::_internal_new_managed() { CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner); // Construct - ctor->invoke_raw(mono_object, NULL); + ctor->invoke_raw(mono_object, nullptr); return mono_object; } void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { + disconnect_event_signals(); + #ifdef DEBUG_ENABLED CRASH_COND(base_ref); - CRASH_COND(gchandle.is_null()); + CRASH_COND(gchandle.is_released()); #endif CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); } @@ -1856,7 +2005,7 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(gchandle.is_null()); + CRASH_COND(gchandle.is_released()); #endif r_remove_script_instance = false; @@ -1886,16 +2035,43 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f } } +void CSharpInstance::connect_event_signals() { + for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) { + const CSharpScript::EventSignal &event_signal = E->value(); + + StringName signal_name = event_signal.field->get_name(); + + // TODO: Use pooling for ManagedCallable instances. + auto event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + + owner->connect(signal_name, Callable(event_signal_callable)); + } +} + +void CSharpInstance::disconnect_event_signals() { + for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) { + const CSharpScript::EventSignal &event_signal = E->value(); + + StringName signal_name = event_signal.field->get_name(); + + // TODO: It would be great if we could store this EventSignalCallable on the stack. + // The problem is that Callable memdeletes it when it's destructed... + auto event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + + owner->disconnect(signal_name, Callable(event_signal_callable)); + } +} + void CSharpInstance::refcount_incremented() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); #endif Reference *ref_owner = Object::cast_to<Reference>(owner); - if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (ref_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // The reference count was increased after the managed side was the only one referencing our owner. @@ -1903,9 +2079,9 @@ void CSharpInstance::refcount_incremented() { // so the owner must hold the managed side alive again to avoid it from being GCed. // Release the current weak handle and replace it with a strong handle. - uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target()); - gchandle->release(); - gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); + MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(gchandle.get_target()); + gchandle.release(); + gchandle = strong_gchandle; } } @@ -1913,23 +2089,23 @@ bool CSharpInstance::refcount_decremented() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); #endif Reference *ref_owner = Object::cast_to<Reference>(owner); int refcount = ref_owner->reference_get_count(); - if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (refcount == 1 && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. // Release the current strong handle and replace it with a weak handle. - uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target()); - gchandle->release(); - gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); + MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(gchandle.get_target()); + gchandle.release(); + gchandle = weak_gchandle; return false; } @@ -2005,7 +2181,7 @@ void CSharpInstance::notification(int p_notification) { MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL(mono_object); - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::dispose(mono_object, &exc); if (exc) { @@ -2050,13 +2226,13 @@ String CSharpInstance::to_string(bool *r_valid) { MonoObject *mono_object = get_mono_object(); - if (mono_object == NULL) { + if (mono_object == nullptr) { if (r_valid) *r_valid = false; return String(); } - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc); if (exc) { @@ -2066,7 +2242,7 @@ String CSharpInstance::to_string(bool *r_valid) { return String(); } - if (result == NULL) { + if (result == nullptr) { if (r_valid) *r_valid = false; return String(); @@ -2085,13 +2261,8 @@ ScriptLanguage *CSharpInstance::get_language() { return CSharpLanguage::get_singleton(); } -CSharpInstance::CSharpInstance() : - owner(NULL), - base_ref(false), - ref_dying(false), - unsafe_referenced(false), - predelete_notified(false), - destructing_script_instance(false) { +CSharpInstance::CSharpInstance(const Ref<CSharpScript> &p_script) : + script(p_script) { } CSharpInstance::~CSharpInstance() { @@ -2100,18 +2271,18 @@ CSharpInstance::~CSharpInstance() { destructing_script_instance = true; - if (gchandle.is_valid()) { + if (!gchandle.is_released()) { if (!predelete_notified && !ref_dying) { // This destructor is not called from the owners destructor. // This could be being called from the owner's set_script_instance method, // meaning this script is being replaced with another one. If this is the case, - // we must call Dispose here, because Dispose calls owner->set_script_instance(NULL) + // we must call Dispose here, because Dispose calls owner->set_script_instance(nullptr) // and that would mess up with the new script instance if called later. - MonoObject *mono_object = gchandle->get_target(); + MonoObject *mono_object = gchandle.get_target(); if (mono_object) { - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::dispose(mono_object, &exc); if (exc) { @@ -2120,7 +2291,7 @@ CSharpInstance::~CSharpInstance() { } } - gchandle->release(); // Make sure the gchandle is released + gchandle.release(); // Make sure the gchandle is released } // If not being called from the owner's destructor, and we still hold a reference to the owner @@ -2141,7 +2312,7 @@ CSharpInstance::~CSharpInstance() { CRASH_COND(die == true); // `owner_keep_alive` holds a reference, so it can't die void *data = owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); - CRASH_COND(data == NULL); + CRASH_COND(data == nullptr); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); @@ -2280,23 +2451,23 @@ bool CSharpScript::_update_exports() { return false; } - uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed) + uint32_t tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed) GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - ERR_FAIL_NULL_V_MSG(ctor, NULL, + ERR_FAIL_NULL_V_MSG(ctor, false, "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); - MonoException *ctor_exc = NULL; - ctor->invoke(tmp_object, NULL, &ctor_exc); + MonoException *ctor_exc = nullptr; + ctor->invoke(tmp_object, nullptr, &ctor_exc); Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); if (ctor_exc) { // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? - MonoGCHandle::free_handle(tmp_pinned_gchandle); - tmp_object = NULL; + GDMonoUtils::free_gchandle(tmp_pinned_gchandle); + tmp_object = nullptr; ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); GDMonoUtils::debug_print_unhandled_exception(ctor_exc); @@ -2343,7 +2514,7 @@ bool CSharpScript::_update_exports() { exported_members_cache.push_front(prop_info); if (tmp_object) { - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoObject *ret = property->get_value(tmp_object, &exc); if (exc) { exported_members_defval_cache[member_name] = Variant(); @@ -2362,11 +2533,11 @@ bool CSharpScript::_update_exports() { } // Need to check this here, before disposal - bool base_ref = Object::cast_to<Reference>(tmp_native) != NULL; + bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr; // Dispose the temporary managed instance - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::dispose(tmp_object, &exc); if (exc) { @@ -2374,8 +2545,8 @@ bool CSharpScript::_update_exports() { GDMonoUtils::debug_print_unhandled_exception(exc); } - MonoGCHandle::free_handle(tmp_pinned_gchandle); - tmp_object = NULL; + GDMonoUtils::free_gchandle(tmp_pinned_gchandle); + tmp_object = nullptr; if (tmp_native && !base_ref) { Node *node = Object::cast_to<Node>(tmp_native); @@ -2414,6 +2585,7 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati // make sure this classes signals are empty when loading for the first time _signals.clear(); + event_signals.clear(); GD_MONO_SCOPE_THREAD_ATTACH; @@ -2421,56 +2593,90 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati while (top && top != p_native_class) { const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); for (int i = delegates.size() - 1; i >= 0; --i) { - Vector<Argument> parameters; - GDMonoClass *delegate = delegates[i]; - if (_get_signal(top, delegate, parameters)) { + if (!delegate->has_attribute(CACHED_CLASS(SignalAttribute))) + continue; + + // Arguments are accessibles as arguments of .Invoke method + GDMonoMethod *invoke_method = delegate->get_method(mono_get_delegate_invoke(delegate->get_mono_ptr())); + + Vector<SignalParameter> parameters; + if (_get_signal(top, invoke_method, parameters)) { _signals[delegate->get_name()] = parameters; } } + List<StringName> found_event_signals; + + void *iter = nullptr; + MonoEvent *raw_event = nullptr; + while ((raw_event = mono_class_get_events(top->get_mono_ptr(), &iter)) != nullptr) { + MonoCustomAttrInfo *event_attrs = mono_custom_attrs_from_event(top->get_mono_ptr(), raw_event); + if (event_attrs) { + if (mono_custom_attrs_has_attr(event_attrs, CACHED_CLASS(SignalAttribute)->get_mono_ptr())) { + const char *event_name = mono_event_get_name(raw_event); + found_event_signals.push_back(StringName(event_name)); + } + + mono_custom_attrs_free(event_attrs); + } + } + + const Vector<GDMonoField *> &fields = top->get_all_fields(); + for (int i = 0; i < fields.size(); i++) { + GDMonoField *field = fields[i]; + + GDMonoClass *field_class = field->get_type().type_class; + + if (!mono_class_is_delegate(field_class->get_mono_ptr())) + continue; + + if (!found_event_signals.find(field->get_name())) + continue; + + GDMonoMethod *invoke_method = field_class->get_method(mono_get_delegate_invoke(field_class->get_mono_ptr())); + + Vector<SignalParameter> parameters; + if (_get_signal(top, invoke_method, parameters)) { + event_signals[field->get_name()] = { field, invoke_method, parameters }; + } + } + top = top->get_parent_class(); } signals_invalidated = false; } -bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { +bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms) { GD_MONO_ASSERT_THREAD_ATTACHED; - if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { - MonoType *raw_type = p_delegate->get_mono_type(); + Vector<StringName> names; + Vector<ManagedType> types; + p_delegate_invoke->get_parameter_names(names); + p_delegate_invoke->get_parameter_types(types); - if (mono_type_get_type(raw_type) == MONO_TYPE_CLASS) { - // Arguments are accessibles as arguments of .Invoke method - GDMonoMethod *invoke = p_delegate->get_method("Invoke", -1); - - Vector<StringName> names; - Vector<ManagedType> types; - invoke->get_parameter_names(names); - invoke->get_parameter_types(types); - - if (names.size() == types.size()) { - for (int i = 0; i < names.size(); ++i) { - Argument arg; - arg.name = names[i]; - arg.type = GDMonoMarshal::managed_to_variant_type(types[i]); - - if (arg.type == Variant::NIL) { - ERR_PRINT("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); - return false; - } + for (int i = 0; i < names.size(); ++i) { + SignalParameter arg; + arg.name = names[i]; - params.push_back(arg); - } + bool nil_is_variant = false; + arg.type = GDMonoMarshal::managed_to_variant_type(types[i], &nil_is_variant); - return true; + if (arg.type == Variant::NIL) { + if (nil_is_variant) { + arg.nil_is_variant = true; + } else { + ERR_PRINT("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); + return false; } } + + params.push_back(arg); } - return false; + return true; } #ifdef TOOLS_ENABLED @@ -2521,7 +2727,8 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect } } - Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type); + bool nil_is_variant = false; + Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type, &nil_is_variant); if (!p_inspect_export || !exported) { r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); @@ -2534,7 +2741,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect PropertyHint hint = PROPERTY_HINT_NONE; String hint_string; - if (variant_type == Variant::NIL) { + if (variant_type == Variant::NIL && !nil_is_variant) { ERR_PRINT("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } @@ -2550,7 +2757,14 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect 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); + uint32_t prop_usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE; + + if (variant_type == Variant::NIL) { + // System.Object (Variant) + prop_usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + + r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, prop_usage); r_exported = true; return true; @@ -2560,6 +2774,11 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { + if (p_variant_type == Variant::NIL) { + // System.Object (Variant) + return 1; + } + GD_MONO_ASSERT_THREAD_ATTACHED; if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) { @@ -2592,7 +2811,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage // Instead of using mono_field_get_value_object, we can do this without boxing. Check the // internal mono functions: ves_icall_System_Enum_GetEnumValuesAndNames and the get_enum_field. - MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL); + MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, nullptr); ERR_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value."); @@ -2616,10 +2835,10 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage } } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) { GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class); - CRASH_COND(field_native_class == NULL); + CRASH_COND(field_native_class == nullptr); r_hint = PROPERTY_HINT_RESOURCE_TYPE; - r_hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class); + r_hint_string = String(NATIVE_GDMONOCLASS_NAME(field_native_class)); } else if (p_allow_generics && p_variant_type == Variant::ARRAY) { // Nested arrays are not supported in the inspector @@ -2658,14 +2877,14 @@ void CSharpScript::_clear() { tool = false; valid = false; - base = NULL; - native = NULL; - script_class = NULL; + base = nullptr; + native = nullptr; + script_class = nullptr; } Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (unlikely(GDMono::get_singleton() == NULL)) { + if (unlikely(GDMono::get_singleton() == nullptr)) { // Probably not the best error but eh. r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return Variant(); @@ -2679,7 +2898,7 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i GDMonoMethod *method = top->get_method(p_method, p_argcount); if (method && method->is_static()) { - MonoObject *result = method->invoke(NULL, p_args); + MonoObject *result = method->invoke(nullptr, p_args); if (result) { return GDMonoMarshal::mono_object_to_variant(result); @@ -2741,7 +2960,7 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD // This method should not fail, only assertions allowed - CRASH_COND(p_class == NULL); + CRASH_COND(p_class == nullptr); // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time Ref<CSharpScript> script = memnew(CSharpScript); @@ -2755,13 +2974,13 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon // This method should not fail, only assertions allowed - CRASH_COND(p_class == NULL); + CRASH_COND(p_class == nullptr); p_script->name = p_class->get_name(); p_script->script_class = p_class; p_script->native = p_native; - CRASH_COND(p_script->native == NULL); + CRASH_COND(p_script->native == nullptr); GDMonoClass *base = p_script->script_class->get_parent_class(); @@ -2818,22 +3037,6 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon bool CSharpScript::can_instance() const { #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - - // Hack to lower the risk of attached scripts not being added to the C# project - if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted... - if (_create_project_solution_if_needed()) { - CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(), - "Compile", - ProjectSettings::get_singleton()->globalize_path(get_path())); - } else { - ERR_PRINT("C# project could not be created; cannot add file: '" + get_path() + "'."); - } - } - } -#endif - -#ifdef TOOLS_ENABLED bool extra_cond = tool || ScriptServer::is_scripting_enabled(); #else bool extra_cond = true; @@ -2843,12 +3046,12 @@ bool CSharpScript::can_instance() const { // For tool scripts, this will never fire if the class is not found. That's because we // don't know if it's a tool script if we can't find the class to access the attributes. if (extra_cond && !script_class) { - if (GDMono::get_singleton()->get_project_assembly() == NULL) { + if (GDMono::get_singleton()->get_project_assembly() == nullptr) { // The project assembly is not loaded - ERR_FAIL_V_MSG(NULL, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); + ERR_FAIL_V_MSG(false, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); } else { // The project assembly is loaded, but the class could not found - ERR_FAIL_V_MSG(NULL, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); + ERR_FAIL_V_MSG(false, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); } } @@ -2871,13 +3074,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); - if (ctor == NULL) { - ERR_FAIL_COND_V_MSG(p_argcount == 0, NULL, + if (ctor == nullptr) { + ERR_FAIL_COND_V_MSG(p_argcount == 0, nullptr, "Cannot create script instance. The class '" + script_class->get_full_name() + "' does not define a parameterless constructor." + (get_path().empty() ? String() : " Path: '" + get_path() + "'.")); - ERR_FAIL_V_MSG(NULL, "Constructor not found."); + ERR_FAIL_V_MSG(nullptr, "Constructor not found."); } Ref<Reference> ref; @@ -2889,13 +3092,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg // If the object had a script instance binding, dispose it before adding the CSharpInstance if (p_owner->has_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index())) { void *data = p_owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); - CRASH_COND(data == NULL); + CRASH_COND(data == nullptr); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - if (script_binding.inited && script_binding.gchandle.is_valid()) { - MonoObject *mono_object = script_binding.gchandle->get_target(); + if (script_binding.inited && !script_binding.gchandle.is_released()) { + MonoObject *mono_object = script_binding.gchandle.get_target(); if (mono_object) { - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::dispose(mono_object, &exc); if (exc) { @@ -2903,13 +3106,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg } } + script_binding.gchandle.release(); // Just in case script_binding.inited = false; } } - CSharpInstance *instance = memnew(CSharpInstance); + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(this))); instance->base_ref = p_isref; - instance->script = Ref<CSharpScript>(this); instance->owner = p_owner; instance->owner->set_script_instance(instance); @@ -2920,19 +3123,19 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg if (!mono_object) { // Important to clear this before destroying the script instance here instance->script = Ref<CSharpScript>(); - instance->owner = NULL; + instance->owner = nullptr; bool die = instance->_unreference_owner_unsafe(); // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. CRASH_COND(die == true); - p_owner->set_script_instance(NULL); + p_owner->set_script_instance(nullptr); r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); + ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); } // Tie managed to unmanaged - instance->gchandle = MonoGCHandle::create_strong(mono_object); + instance->gchandle = MonoGCHandleData::new_strong_handle(mono_object); if (instance->base_ref) instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) @@ -2974,7 +3177,7 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal ref = REF(r); } - CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != NULL, r_error); + CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != nullptr, r_error); if (!instance) { if (ref.is_null()) { memdelete(owner); //no owner, sorry @@ -2996,20 +3199,22 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { #endif if (native) { - String native_name = NATIVE_GDMONOCLASS_NAME(native); + StringName native_name = NATIVE_GDMONOCLASS_NAME(native); if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { - if (ScriptDebugger::get_singleton()) { - CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + if (EngineDebugger::is_active()) { + CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, + "Script inherits from native type '" + String(native_name) + + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); } - ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + native_name + - "', so it can't be instanced in object of type: '" + p_this->get_class() + "'."); + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'."); } } GD_MONO_SCOPE_THREAD_ATTACH; Callable::CallError unchecked_error; - return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error); + return _create_instance(nullptr, 0, p_this, Object::cast_to<Reference>(p_this) != nullptr, unchecked_error); } PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) { @@ -3020,7 +3225,7 @@ PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_t _update_exports(); return si; #else - return NULL; + return nullptr; #endif } @@ -3128,7 +3333,7 @@ Error CSharpScript::reload(bool p_keep_state) { script_class = project_assembly->get_object_derived_class(name); } - valid = script_class != NULL; + valid = script_class != nullptr; if (script_class) { #ifdef DEBUG_ENABLED @@ -3150,7 +3355,7 @@ Error CSharpScript::reload(bool p_keep_state) { native = GDMonoUtils::get_class_native_base(script_class); - CRASH_COND(native == NULL); + CRASH_COND(native == nullptr); GDMonoClass *base_class = script_class->get_parent_class(); @@ -3288,19 +3493,45 @@ void CSharpScript::update_exports() { } bool CSharpScript::has_script_signal(const StringName &p_signal) const { - return _signals.has(p_signal); + return event_signals.has(p_signal) || _signals.has(p_signal); } void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { - for (const Map<StringName, Vector<Argument> >::Element *E = _signals.front(); E; E = E->next()) { + + for (const Map<StringName, Vector<SignalParameter>>::Element *E = _signals.front(); E; E = E->next()) { MethodInfo mi; + mi.name = E->key(); + + const Vector<SignalParameter> ¶ms = E->value(); + for (int i = 0; i < params.size(); i++) { + const SignalParameter ¶m = params[i]; + + PropertyInfo arg_info = PropertyInfo(param.type, param.name); + if (param.type == Variant::NIL && param.nil_is_variant) + arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + + mi.arguments.push_back(arg_info); + } + + r_signals->push_back(mi); + } + for (const Map<StringName, EventSignal>::Element *E = event_signals.front(); E; E = E->next()) { + MethodInfo mi; mi.name = E->key(); - for (int i = 0; i < E->get().size(); i++) { - PropertyInfo arg; - arg.name = E->get()[i].name; - mi.arguments.push_back(arg); + + const EventSignal &event_signal = E->value(); + const Vector<SignalParameter> ¶ms = event_signal.parameters; + for (int i = 0; i < params.size(); i++) { + const SignalParameter ¶m = params[i]; + + PropertyInfo arg_info = PropertyInfo(param.type, param.name); + if (param.type == Variant::NIL && param.nil_is_variant) + arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + + mi.arguments.push_back(arg_info); } + r_signals->push_back(mi); } } @@ -3418,19 +3649,10 @@ StringName CSharpScript::get_script_name() const { return name; } -CSharpScript::CSharpScript() : - script_list(this) { +CSharpScript::CSharpScript() { _clear(); -#ifdef TOOLS_ENABLED - source_changed_cache = false; - placeholder_fallback_enabled = false; - exports_invalidated = true; -#endif - - signals_invalidated = true; - _resource_path_changed(); #ifdef DEBUG_ENABLED @@ -3545,7 +3767,7 @@ void ResourceFormatSaverCSharpScript::get_recognized_extensions(const RES &p_res bool ResourceFormatSaverCSharpScript::recognize(const RES &p_resource) const { - return Object::cast_to<CSharpScript>(p_resource.ptr()) != NULL; + return Object::cast_to<CSharpScript>(p_resource.ptr()) != nullptr; } CSharpLanguage::StringNameCache::StringNameCache() { @@ -3559,4 +3781,5 @@ CSharpLanguage::StringNameCache::StringNameCache() { on_before_serialize = StaticCString::create("OnBeforeSerialize"); on_after_deserialize = StaticCString::create("OnAfterDeserialize"); dotctor = StaticCString::create(".ctor"); + delegate_invoke_method_name = StaticCString::create("Invoke"); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 18c53aab52..29c33b50bb 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -53,8 +53,8 @@ class CSharpLanguage; template <typename TScriptInstance, typename TScriptLanguage> TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { if (!p_inst) - return NULL; - return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : NULL; + return nullptr; + return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : nullptr; } #else template <typename TScriptInstance, typename TScriptLanguage> @@ -69,18 +69,32 @@ class CSharpScript : public Script { GDCLASS(CSharpScript, Script); +public: + struct SignalParameter { + String name; + Variant::Type type; + bool nil_is_variant = false; + }; + + struct EventSignal { + GDMonoField *field = nullptr; + GDMonoMethod *invoke_method = nullptr; + Vector<SignalParameter> parameters; + }; + +private: friend class CSharpInstance; friend class CSharpLanguage; friend struct CSharpScriptDepSort; - bool tool; - bool valid; + bool tool = false; + bool valid = false; bool builtin; - GDMonoClass *base; - GDMonoClass *native; - GDMonoClass *script_class; + GDMonoClass *base = nullptr; + GDMonoClass *native = nullptr; + GDMonoClass *script_class = nullptr; Ref<CSharpScript> base_cache; // TODO what's this for? @@ -91,7 +105,8 @@ class CSharpScript : public Script { // TODO // Replace with buffer containing the serialized state of managed scripts. // Keep variant state backup to use only with script instance placeholders. - List<Pair<StringName, Variant> > properties; + List<Pair<StringName, Variant>> properties; + List<Pair<StringName, Array>> event_signals; }; Set<ObjectID> pending_reload_instances; @@ -103,15 +118,11 @@ class CSharpScript : public Script { String source; StringName name; - SelfList<CSharpScript> script_list; - - struct Argument { - String name; - Variant::Type type; - }; + SelfList<CSharpScript> script_list = this; - Map<StringName, Vector<Argument> > _signals; - bool signals_invalidated; + Map<StringName, Vector<SignalParameter>> _signals; + Map<StringName, EventSignal> event_signals; + bool signals_invalidated = true; Vector<ScriptNetData> rpc_functions; Vector<ScriptNetData> rpc_variables; @@ -120,9 +131,9 @@ class CSharpScript : public Script { List<PropertyInfo> exported_members_cache; // members_cache Map<StringName, Variant> exported_members_defval_cache; // member_default_values_cache Set<PlaceHolderScriptInstance *> placeholders; - bool source_changed_cache; - bool placeholder_fallback_enabled; - bool exports_invalidated; + bool source_changed_cache = false; + bool placeholder_fallback_enabled = false; + bool exports_invalidated = true; void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames); void _update_member_info_no_exports(); virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); @@ -133,7 +144,7 @@ class CSharpScript : public Script { void _clear(); void load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class); - bool _get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms); + bool _get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms); bool _update_exports(); #ifdef TOOLS_ENABLED @@ -221,15 +232,17 @@ class CSharpInstance : public ScriptInstance { friend class CSharpScript; friend class CSharpLanguage; - Object *owner; - bool base_ref; - bool ref_dying; - bool unsafe_referenced; - bool predelete_notified; - bool destructing_script_instance; + Object *owner = nullptr; + bool base_ref = false; + bool ref_dying = false; + bool unsafe_referenced = false; + bool predelete_notified = false; + bool destructing_script_instance = false; Ref<CSharpScript> script; - Ref<MonoGCHandle> gchandle; + MonoGCHandleData gchandle; + + Vector<Callable> event_signal_callables; bool _reference_owner_unsafe(); @@ -239,17 +252,18 @@ class CSharpInstance : public ScriptInstance { bool _unreference_owner_unsafe(); /* - * If NULL is returned, the caller must destroy the script instance by removing it from its owner. + * If nullptr is returned, the caller must destroy the script instance by removing it from its owner. */ MonoObject *_internal_new_managed(); // Do not use unless you know what you are doing friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); - static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle); + static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle); void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount); - void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state); + void get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state); + void get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state); public: MonoObject *get_mono_object() const; @@ -272,11 +286,14 @@ public: void mono_object_disposed(MonoObject *p_obj); /* - * If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, if + * If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, ifevent_signal * 'r_remove_script_instance' is set to true, the caller must destroy the script instance by removing it from its owner. */ void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance); + void connect_event_signals(); + void disconnect_event_signals(); + virtual void refcount_incremented(); virtual bool refcount_decremented(); @@ -301,7 +318,7 @@ public: virtual ScriptLanguage *get_language(); - CSharpInstance(); + CSharpInstance(const Ref<CSharpScript> &p_script); ~CSharpInstance(); }; @@ -309,8 +326,18 @@ struct CSharpScriptBinding { bool inited; StringName type_name; GDMonoClass *wrapper_class; - Ref<MonoGCHandle> gchandle; + MonoGCHandleData gchandle; Object *owner; + + CSharpScriptBinding() : + inited(false), + wrapper_class(nullptr), + owner(nullptr) { + } +}; + +class ManagedCallableMiddleman : public Object { + GDCLASS(ManagedCallableMiddleman, Object); }; class CSharpLanguage : public ScriptLanguage { @@ -320,9 +347,10 @@ class CSharpLanguage : public ScriptLanguage { static CSharpLanguage *singleton; - bool finalizing; + bool finalizing = false; + bool finalized = false; - GDMono *gdmono; + GDMono *gdmono = nullptr; SelfList<CSharpScript>::List script_list; Mutex script_instances_mutex; @@ -337,6 +365,8 @@ class CSharpLanguage : public ScriptLanguage { Mutex unsafe_object_references_lock; #endif + ManagedCallableMiddleman *managed_callable_middleman = memnew(ManagedCallableMiddleman); + struct StringNameCache { StringName _signal_callback; @@ -348,17 +378,18 @@ class CSharpLanguage : public ScriptLanguage { StringName dotctor; // .ctor StringName on_before_serialize; // OnBeforeSerialize StringName on_after_deserialize; // OnAfterDeserialize + StringName delegate_invoke_method_name; StringNameCache(); }; - int lang_idx; + int lang_idx = -1; Dictionary scripts_metadata; - bool scripts_metadata_invalidated; + bool scripts_metadata_invalidated = true; // For debug_break and debug_break_parse - int _debug_parse_err_line; + int _debug_parse_err_line = -1; String _debug_parse_err_file; String _debug_error; @@ -368,7 +399,7 @@ class CSharpLanguage : public ScriptLanguage { void _on_scripts_domain_unloaded(); #ifdef TOOLS_ENABLED - EditorPlugin *godotsharp_editor; + EditorPlugin *godotsharp_editor = nullptr; static void _editor_init_callback(); #endif @@ -389,8 +420,8 @@ public: _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; } #endif - static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle); - static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle); + static void release_script_gchandle(MonoGCHandleData &p_gchandle); + static void release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle); bool debug_break(const String &p_error, bool p_allow_continue = true); bool debug_break_parse(const String &p_file, int p_line, const String &p_error); @@ -410,6 +441,8 @@ public: return scripts_metadata; } + _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; } + virtual String get_name() const; /* LANGUAGE FUNCTIONS */ @@ -426,7 +459,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings = NULL, Set<int> *r_safe_lines = NULL) const { return true; } + /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const { return true; } virtual String validate_path(const String &p_path) const; virtual Script *create_script() const; virtual bool has_named_classes() const; @@ -458,7 +491,7 @@ public: virtual void frame(); /* TODO? */ virtual void get_public_functions(List<MethodInfo> *p_functions) const {} - /* TODO? */ virtual void get_public_constants(List<Pair<String, Variant> > *p_constants) const {} + /* TODO? */ virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const {} virtual void reload_all_scripts(); virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload); @@ -497,7 +530,7 @@ public: class ResourceFormatLoaderCSharpScript : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL, bool p_use_sub_threads = false, float *r_progress = nullptr); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs index 8717c4901e..4374f21cfa 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index b531b6aeee..326c49f096 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -30,7 +30,7 @@ namespace GodotTools.Core path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); - return rooted ? Path.DirectorySeparatorChar.ToString() + path : path; + return rooted ? Path.DirectorySeparatorChar + path : path; } private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory); diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs index c7c00e66a2..0806d02ca0 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs index 76cb249acf..9afd9adeb1 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -1,6 +1,8 @@ using GodotTools.Core; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text.RegularExpressions; namespace GodotTools.ProjectEditor { @@ -118,5 +120,40 @@ EndProject"; const string ProjectPlatformsConfig = @" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; + + public static void MigrateFromOldConfigNames(string slnPath) + { + if (!File.Exists(slnPath)) + return; + + var input = File.ReadAllText(slnPath); + + if (!Regex.IsMatch(input, Regex.Escape("Tools|Any CPU"))) + return; + + // This method renames old configurations in solutions to the new ones. + // + // This is the order configs appear in the solution and what we want to rename them to: + // Debug|Any CPU = Debug|Any CPU -> ExportDebug|Any CPU = ExportDebug|Any CPU + // Tools|Any CPU = Tools|Any CPU -> Debug|Any CPU = Debug|Any CPU + // + // But we want to move Tools (now Debug) to the top, so it's easier to rename like this: + // Debug|Any CPU = Debug|Any CPU -> Debug|Any CPU = Debug|Any CPU + // Release|Any CPU = Release|Any CPU -> ExportDebug|Any CPU = ExportDebug|Any CPU + // Tools|Any CPU = Tools|Any CPU -> ExportRelease|Any CPU = ExportRelease|Any CPU + + var dict = new Dictionary<string, string> + { + {"Debug|Any CPU", "Debug|Any CPU"}, + {"Release|Any CPU", "ExportDebug|Any CPU"}, + {"Tools|Any CPU", "ExportRelease|Any CPU"} + }; + + var regex = new Regex(string.Join("|",dict.Keys.Select(Regex.Escape))); + var result = regex.Replace(input,m => dict[m.Value]); + + if (result != input) + File.WriteAllText(slnPath, result); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs index 36961eb45e..f0e0d1b33d 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs @@ -1,5 +1,7 @@ using GodotTools.Core; using System; +using System.Collections.Generic; +using System.IO; using DotNet.Globbing; using Microsoft.Build.Construction; @@ -7,16 +9,15 @@ namespace GodotTools.ProjectEditor { public static class ProjectExtensions { - public static bool HasItem(this ProjectRootElement root, string itemType, string include) + public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false) { - GlobOptions globOptions = new GlobOptions(); - globOptions.Evaluation.CaseInsensitive = false; + GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}}; string normalizedInclude = include.NormalizePath(); foreach (var itemGroup in root.ItemGroups) { - if (itemGroup.Condition.Length != 0) + if (noCondition && itemGroup.Condition.Length != 0) continue; foreach (var item in itemGroup.Items) @@ -27,18 +28,65 @@ namespace GodotTools.ProjectEditor var glob = Glob.Parse(item.Include.NormalizePath(), globOptions); if (glob.IsMatch(normalizedInclude)) - { - return true; - } + return item; } } - return false; + return null; + } + public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false) + { + GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}}; + + string normalizedInclude = Path.GetFullPath(include).NormalizePath(); + + foreach (var itemGroup in root.ItemGroups) + { + if (noCondition && itemGroup.Condition.Length != 0) + continue; + + foreach (var item in itemGroup.Items) + { + if (item.ItemType != itemType) + continue; + + var glob = Glob.Parse(Path.GetFullPath(item.Include).NormalizePath(), globOptions); + + if (glob.IsMatch(normalizedInclude)) + return item; + } + } + + return null; + } + + public static IEnumerable<ProjectItemElement> FindAllItemsInFolder(this ProjectRootElement root, string itemType, string folder) + { + string absFolderNormalizedWithSep = Path.GetFullPath(folder).NormalizePath() + Path.DirectorySeparatorChar; + + foreach (var itemGroup in root.ItemGroups) + { + foreach (var item in itemGroup.Items) + { + if (item.ItemType != itemType) + continue; + + string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath(); + + if (absPathNormalized.StartsWith(absFolderNormalizedWithSep)) + yield return item; + } + } + } + + public static bool HasItem(this ProjectRootElement root, string itemType, string include, bool noCondition = false) + { + return root.FindItemOrNull(itemType, include, noCondition) != null; } public static bool AddItemChecked(this ProjectRootElement root, string itemType, string include) { - if (!root.HasItem(itemType, include)) + if (!root.HasItem(itemType, include, noCondition: true)) { root.AddItem(itemType, include); return true; @@ -47,6 +95,18 @@ namespace GodotTools.ProjectEditor return false; } + public static bool RemoveItemChecked(this ProjectRootElement root, string itemType, string include) + { + var item = root.FindItemOrNullAbs(itemType, include); + if (item != null) + { + item.Parent.RemoveChild(item); + return true; + } + + return false; + } + public static Guid GetGuid(this ProjectRootElement root) { foreach (var property in root.Properties) diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 28b7832f90..cbe3afaedd 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -17,30 +17,30 @@ namespace GodotTools.ProjectEditor string path = Path.Combine(dir, name + ".csproj"); ProjectPropertyGroupElement mainGroup; - var root = CreateLibraryProject(name, "Tools", out mainGroup); + var root = CreateLibraryProject(name, "Debug", out mainGroup); mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); - mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' "; - mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' "; - - var toolsGroup = root.AddPropertyGroup(); - toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' "; - toolsGroup.AddProperty("DebugSymbols", "true"); - toolsGroup.AddProperty("DebugType", "portable"); - toolsGroup.AddProperty("Optimize", "false"); - toolsGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;"); - toolsGroup.AddProperty("ErrorReport", "prompt"); - toolsGroup.AddProperty("WarningLevel", "4"); - toolsGroup.AddProperty("ConsolePause", "false"); + mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'ExportRelease' "; + mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'ExportRelease' "; + + var debugGroup = root.AddPropertyGroup(); + debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "; + debugGroup.AddProperty("DebugSymbols", "true"); + debugGroup.AddProperty("DebugType", "portable"); + debugGroup.AddProperty("Optimize", "false"); + debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;"); + debugGroup.AddProperty("ErrorReport", "prompt"); + debugGroup.AddProperty("WarningLevel", "4"); + debugGroup.AddProperty("ConsolePause", "false"); var coreApiRef = root.AddItem("Reference", CoreApiProjectName); coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("Private", "False"); var editorApiRef = root.AddItem("Reference", EditorApiProjectName); - editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; + editorApiRef.Condition = " '$(Configuration)' == 'Debug' "; editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("Private", "False"); @@ -103,24 +103,24 @@ namespace GodotTools.ProjectEditor mainGroup.AddProperty("TargetFrameworkVersion", "v4.7"); mainGroup.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString()); - var debugGroup = root.AddPropertyGroup(); - debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "; - debugGroup.AddProperty("DebugSymbols", "true"); - debugGroup.AddProperty("DebugType", "portable"); - debugGroup.AddProperty("Optimize", "false"); - debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;"); - debugGroup.AddProperty("ErrorReport", "prompt"); - debugGroup.AddProperty("WarningLevel", "4"); - debugGroup.AddProperty("ConsolePause", "false"); - - var releaseGroup = root.AddPropertyGroup(); - releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "; - releaseGroup.AddProperty("DebugType", "portable"); - releaseGroup.AddProperty("Optimize", "true"); - releaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;"); - releaseGroup.AddProperty("ErrorReport", "prompt"); - releaseGroup.AddProperty("WarningLevel", "4"); - releaseGroup.AddProperty("ConsolePause", "false"); + var exportDebugGroup = root.AddPropertyGroup(); + exportDebugGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportDebug|AnyCPU' "; + exportDebugGroup.AddProperty("DebugSymbols", "true"); + exportDebugGroup.AddProperty("DebugType", "portable"); + exportDebugGroup.AddProperty("Optimize", "false"); + exportDebugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;"); + exportDebugGroup.AddProperty("ErrorReport", "prompt"); + exportDebugGroup.AddProperty("WarningLevel", "4"); + exportDebugGroup.AddProperty("ConsolePause", "false"); + + var exportReleaseGroup = root.AddPropertyGroup(); + exportReleaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportRelease|AnyCPU' "; + exportReleaseGroup.AddProperty("DebugType", "portable"); + exportReleaseGroup.AddProperty("Optimize", "true"); + exportReleaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;"); + exportReleaseGroup.AddProperty("ErrorReport", "prompt"); + exportReleaseGroup.AddProperty("WarningLevel", "4"); + exportReleaseGroup.AddProperty("ConsolePause", "false"); // References var referenceGroup = root.AddItemGroup(); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 233aab45b3..1776b46e6a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using DotNet.Globbing; using Microsoft.Build.Construction; @@ -22,6 +23,79 @@ namespace GodotTools.ProjectEditor root.Save(); } + public static void RenameItemInProjectChecked(string projectPath, string itemType, string oldInclude, string newInclude) + { + var dir = Directory.GetParent(projectPath).FullName; + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + var normalizedOldInclude = oldInclude.NormalizePath(); + var normalizedNewInclude = newInclude.NormalizePath(); + + var item = root.FindItemOrNullAbs(itemType, normalizedOldInclude); + + if (item == null) + return; + + item.Include = normalizedNewInclude.RelativeToPath(dir).Replace("/", "\\"); + root.Save(); + } + + public static void RemoveItemFromProjectChecked(string projectPath, string itemType, string include) + { + var dir = Directory.GetParent(projectPath).FullName; + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + var normalizedInclude = include.NormalizePath(); + + if (root.RemoveItemChecked(itemType, normalizedInclude)) + root.Save(); + } + + public static void RenameItemsToNewFolderInProjectChecked(string projectPath, string itemType, string oldFolder, string newFolder) + { + var dir = Directory.GetParent(projectPath).FullName; + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + bool dirty = false; + + var oldFolderNormalized = oldFolder.NormalizePath(); + var newFolderNormalized = newFolder.NormalizePath(); + string absOldFolderNormalized = Path.GetFullPath(oldFolderNormalized).NormalizePath(); + string absNewFolderNormalized = Path.GetFullPath(newFolderNormalized).NormalizePath(); + + foreach (var item in root.FindAllItemsInFolder(itemType, oldFolderNormalized)) + { + string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath(); + string absNewIncludeNormalized = absNewFolderNormalized + absPathNormalized.Substring(absOldFolderNormalized.Length); + item.Include = absNewIncludeNormalized.RelativeToPath(dir).Replace("/", "\\"); + dirty = true; + } + + if (dirty) + root.Save(); + } + + public static void RemoveItemsInFolderFromProjectChecked(string projectPath, string itemType, string folder) + { + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + var folderNormalized = folder.NormalizePath(); + + var itemsToRemove = root.FindAllItemsInFolder(itemType, folderNormalized).ToList(); + + if (itemsToRemove.Count > 0) + { + foreach (var item in itemsToRemove) + item.Parent.RemoveChild(item); + + root.Save(); + } + } + private static string[] GetAllFilesRecursive(string rootDirectory, string mask) { string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories); @@ -44,6 +118,7 @@ namespace GodotTools.ProjectEditor globOptions.Evaluation.CaseInsensitive = false; var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); foreach (var itemGroup in root.ItemGroups) { @@ -85,35 +160,35 @@ namespace GodotTools.ProjectEditor void AddPropertyIfNotPresent(string name, string condition, string value) { if (root.PropertyGroups - .Any(g => (g.Condition == string.Empty || g.Condition == condition) && + .Any(g => (g.Condition == string.Empty || g.Condition.Trim() == condition) && g.Properties .Any(p => p.Name == name && p.Value == value && - (p.Condition == condition || g.Condition == condition)))) + (p.Condition.Trim() == condition || g.Condition.Trim() == condition)))) { return; } - root.AddProperty(name, value).Condition = condition; + root.AddProperty(name, value).Condition = " " + condition + " "; dirty = true; } AddPropertyIfNotPresent(name: "ApiConfiguration", - condition: " '$(Configuration)' != 'Release' ", + condition: "'$(Configuration)' != 'ExportRelease'", value: "Debug"); AddPropertyIfNotPresent(name: "ApiConfiguration", - condition: " '$(Configuration)' == 'Release' ", + condition: "'$(Configuration)' == 'ExportRelease'", value: "Release"); void SetReferenceHintPath(string referenceName, string condition, string hintPath) { foreach (var itemGroup in root.ItemGroups.Where(g => - g.Condition == string.Empty || g.Condition == condition)) + g.Condition.Trim() == string.Empty || g.Condition.Trim() == condition)) { var references = itemGroup.Items.Where(item => item.ItemType == "Reference" && item.Include == referenceName && - (item.Condition == condition || itemGroup.Condition == condition)); + (item.Condition.Trim() == condition || itemGroup.Condition.Trim() == condition)); var referencesWithHintPath = references.Where(reference => reference.Metadata.Any(m => m.Name == "HintPath")); @@ -152,7 +227,7 @@ namespace GodotTools.ProjectEditor } // Found no Reference item at all. Add it. - root.AddItem("Reference", referenceName).Condition = condition; + root.AddItem("Reference", referenceName).Condition = " " + condition + " "; dirty = true; } @@ -160,7 +235,7 @@ namespace GodotTools.ProjectEditor const string editorProjectName = "GodotSharpEditor"; const string coreCondition = ""; - const string editorCondition = " '$(Configuration)' == 'Tools' "; + const string editorCondition = "'$(Configuration)' == 'Debug'"; var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll"; var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll"; @@ -171,5 +246,104 @@ namespace GodotTools.ProjectEditor if (dirty) root.Save(); } + + public static void MigrateFromOldConfigNames(string projectPath) + { + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + bool dirty = false; + + bool hasGodotProjectGeneratorVersion = false; + bool foundOldConfiguration = false; + + foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition == string.Empty)) + { + if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion")) + hasGodotProjectGeneratorVersion = true; + + foreach (var configItem in propertyGroup.Properties + .Where(p => p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Tools")) + { + configItem.Value = "Debug"; + foundOldConfiguration = true; + dirty = true; + } + } + + if (!hasGodotProjectGeneratorVersion) + { + root.PropertyGroups.First(g => g.Condition == string.Empty)? + .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString()); + dirty = true; + } + + if (!foundOldConfiguration) + { + var toolsConditions = new[] + { + "'$(Configuration)|$(Platform)' == 'Tools|AnyCPU'", + "'$(Configuration)|$(Platform)' != 'Tools|AnyCPU'", + "'$(Configuration)' == 'Tools'", + "'$(Configuration)' != 'Tools'" + }; + + foundOldConfiguration = root.PropertyGroups + .Any(g => toolsConditions.Any(c => c == g.Condition.Trim())); + } + + if (foundOldConfiguration) + { + void MigrateConfigurationConditions(string oldConfiguration, string newConfiguration) + { + void MigrateConditions(string oldCondition, string newCondition) + { + foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition.Trim() == oldCondition)) + { + propertyGroup.Condition = " " + newCondition + " "; + dirty = true; + } + + foreach (var propertyGroup in root.PropertyGroups) + { + foreach (var prop in propertyGroup.Properties.Where(p => p.Condition.Trim() == oldCondition)) + { + prop.Condition = " " + newCondition + " "; + dirty = true; + } + } + + foreach (var itemGroup in root.ItemGroups.Where(g => g.Condition.Trim() == oldCondition)) + { + itemGroup.Condition = " " + newCondition + " "; + dirty = true; + } + + foreach (var itemGroup in root.ItemGroups) + { + foreach (var item in itemGroup.Items.Where(item => item.Condition.Trim() == oldCondition)) + { + item.Condition = " " + newCondition + " "; + dirty = true; + } + } + } + + foreach (var op in new[] {"==", "!="}) + { + MigrateConditions($"'$(Configuration)|$(Platform)' {op} '{oldConfiguration}|AnyCPU'", $"'$(Configuration)|$(Platform)' {op} '{newConfiguration}|AnyCPU'"); + MigrateConditions($"'$(Configuration)' {op} '{oldConfiguration}'", $"'$(Configuration)' {op} '{newConfiguration}'"); + } + } + + MigrateConfigurationConditions("Debug", "ExportDebug"); + MigrateConfigurationConditions("Release", "ExportRelease"); + MigrateConfigurationConditions("Tools", "Debug"); // Must be last + } + + + if (dirty) + root.Save(); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs index 09333850fc..3a0464c9bc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index bd7eb59913..3cf495f025 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -166,7 +166,7 @@ namespace GodotTools Internal.GodotIs32Bits() ? "32" : "64" }; - bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines); + bool buildSuccess = BuildManager.BuildProjectBlocking("Debug", godotDefines); if (!buildSuccess) return; @@ -205,9 +205,9 @@ namespace GodotTools 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")); + panelTabs.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("DebuggerTabBG", "EditorStyles")); } } @@ -258,9 +258,9 @@ namespace GodotTools RectMinSize = new Vector2(0, 228) * EditorScale, SizeFlagsVertical = (int)SizeFlags.ExpandFill }; - panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("DebuggerTabBG", "EditorStyles")); AddChild(panelTabs); { @@ -280,7 +280,7 @@ namespace GodotTools Text = "Build Project".TTR(), FocusMode = FocusModeEnum.None }; - buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed)); + buildProjectBtn.PressedSignal += BuildProjectPressed; toolBarHBox.AddChild(buildProjectBtn); toolBarHBox.AddSpacer(begin: false); @@ -293,7 +293,7 @@ namespace GodotTools Visible = false, FocusMode = FocusModeEnum.None }; - warningsBtn.Connect("toggled", this, nameof(_WarningsToggled)); + warningsBtn.Toggled += _WarningsToggled; toolBarHBox.AddChild(warningsBtn); errorsBtn = new ToolButton @@ -304,7 +304,7 @@ namespace GodotTools Visible = false, FocusMode = FocusModeEnum.None }; - errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled)); + errorsBtn.Toggled += _ErrorsToggled; toolBarHBox.AddChild(errorsBtn); toolBarHBox.AddSpacer(begin: false); @@ -315,7 +315,7 @@ namespace GodotTools FocusMode = FocusModeEnum.None, Visible = false }; - viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed)); + viewLogBtn.PressedSignal += _ViewLogPressed; toolBarHBox.AddChild(viewLogBtn); var hsc = new HSplitContainer @@ -326,8 +326,8 @@ namespace GodotTools panelBuildsTab.AddChild(hsc); buildTabsList = new ItemList { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; - buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected)); - buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected)); + buildTabsList.ItemSelected += _BuildTabsItemSelected; + buildTabsList.NothingSelected += _BuildTabsNothingSelected; hsc.AddChild(buildTabsList); buildTabs = new TabContainer diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index 69a8c9cf4a..520e665595 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -166,7 +166,7 @@ namespace GodotTools // Make sure the API assemblies are up to date before building the project. // We may not have had the chance to update the release API assemblies, and the debug ones // may have been deleted by the user at some point after they were loaded by the Godot editor. - string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "Release" ? "Release" : "Debug"); + string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "ExportRelease" ? "Release" : "Debug"); if (!string.IsNullOrEmpty(apiAssembliesUpdateError)) { @@ -242,7 +242,7 @@ namespace GodotTools Internal.GodotIs32Bits() ? "32" : "64" }; - return BuildProjectBlocking("Tools", godotDefines); + return BuildProjectBlocking("Debug", godotDefines); } public static void Initialize() @@ -251,12 +251,12 @@ namespace GodotTools var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); var msbuild = BuildTool.MsBuildMono; if (OS.IsWindows) - msbuild = RiderPathManager.IsRider((string) editorSettings.GetSetting(RiderPathManager.EditorPathSettingName)) + msbuild = RiderPathManager.IsExternalEditorSetToRider(editorSettings) ? BuildTool.JetBrainsMsBuild : BuildTool.MsBuildVs; EditorDef("mono/builds/build_tool", msbuild); - + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { ["type"] = Godot.Variant.Type.Int, diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs index f75fe239e3..938c3d8be1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs @@ -46,12 +46,12 @@ namespace GodotTools get { if (!BuildExited) - return GetIcon("Stop", "EditorIcons"); + return GetThemeIcon("Stop", "EditorIcons"); if (BuildResult == BuildResults.Error) - return GetIcon("StatusError", "EditorIcons"); + return GetThemeIcon("StatusError", "EditorIcons"); - return GetIcon("StatusSuccess", "EditorIcons"); + return GetThemeIcon("StatusSuccess", "EditorIcons"); } } @@ -145,8 +145,8 @@ namespace GodotTools { issuesList.Clear(); - using (var warningIcon = GetIcon("Warning", "EditorIcons")) - using (var errorIcon = GetIcon("Error", "EditorIcons")) + using (var warningIcon = GetThemeIcon("Warning", "EditorIcons")) + using (var errorIcon = GetThemeIcon("Error", "EditorIcons")) { for (int i = 0; i < issues.Count; i++) { @@ -251,7 +251,7 @@ namespace GodotTools base._Ready(); issuesList = new ItemList { SizeFlagsVertical = (int)SizeFlags.ExpandFill }; - issuesList.Connect("item_activated", this, nameof(_IssueActivated)); + issuesList.ItemActivated += _IssueActivated; AddChild(issuesList); } diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 9abfda4538..421729cc11 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -32,18 +32,6 @@ namespace GodotTools ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include); } - public static void FixApiHintPath(string projectPath) - { - try - { - ProjectUtils.FixApiHintPath(projectPath); - } - catch (Exception e) - { - GD.PushError(e.ToString()); - } - } - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static ulong ConvertToTimestamp(this DateTime value) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs new file mode 100755 index 0000000000..f1765f7e19 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -0,0 +1,618 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using GodotTools.Internals; +using Directory = GodotTools.Utils.Directory; +using File = GodotTools.Utils.File; +using OS = GodotTools.Utils.OS; +using Path = System.IO.Path; + +namespace GodotTools.Export +{ + public struct AotOptions + { + public bool EnableLLVM; + public bool LLVMOnly; + public string LLVMPath; + public string LLVMOutputPath; + + public bool FullAot; + + private bool _useInterpreter; + public bool UseInterpreter { get => _useInterpreter && !LLVMOnly; set => _useInterpreter = value; } + + public string[] ExtraAotOptions; + public string[] ExtraOptimizerOptions; + + public string ToolchainPath; + } + + public static class AotBuilder + { + public static void CompileAssemblies(ExportPlugin exporter, AotOptions aotOpts, string[] features, string platform, bool isDebug, string bclDir, string outputDir, string outputDataDir, IDictionary<string, string> assemblies) + { + // TODO: WASM + + string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}"); + + if (!Directory.Exists(aotTempDir)) + Directory.CreateDirectory(aotTempDir); + + var assembliesPrepared = new Dictionary<string, string>(); + + foreach (var dependency in assemblies) + { + string assemblyName = dependency.Key; + string assemblyPath = dependency.Value; + + string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll"); + + if (File.Exists(assemblyPathInBcl)) + { + // Don't create teporaries for assemblies from the BCL + assembliesPrepared.Add(assemblyName, assemblyPathInBcl); + } + else + { + string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll"); + File.Copy(assemblyPath, tempAssemblyPath); + assembliesPrepared.Add(assemblyName, tempAssemblyPath); + } + } + + if (platform == OS.Platforms.iOS) + { + var architectures = GetEnablediOSArchs(features).ToArray(); + CompileAssembliesForiOS(exporter, isDebug, architectures, aotOpts, aotTempDir, assembliesPrepared, bclDir); + } + else if (platform == OS.Platforms.Android) + { + var abis = GetEnabledAndroidAbis(features).ToArray(); + CompileAssembliesForAndroid(exporter, isDebug, abis, aotOpts, aotTempDir, assembliesPrepared, bclDir); + } + else + { + string bits = features.Contains("64") ? "64" : features.Contains("32") ? "32" : null; + CompileAssembliesForDesktop(exporter, platform, isDebug, bits, aotOpts, aotTempDir, outputDataDir, assembliesPrepared, bclDir); + } + } + + public static void CompileAssembliesForAndroid(ExportPlugin exporter, bool isDebug, string[] abis, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir) + { + + foreach (var assembly in assemblies) + { + string assemblyName = assembly.Key; + string assemblyPath = assembly.Value; + + // Not sure if the 'lib' prefix is an Android thing or just Godot being picky, + // but we use '-aot-' as well just in case to avoid conflicts with other libs. + string outputFileName = "lib-aot-" + assemblyName + ".dll.so"; + + foreach (string abi in abis) + { + string aotAbiTempDir = Path.Combine(aotTempDir, abi); + string soFilePath = Path.Combine(aotAbiTempDir, outputFileName); + + var compilerArgs = GetAotCompilerArgs(OS.Platforms.Android, isDebug, abi, aotOpts, assemblyPath, soFilePath); + + // Make sure the output directory exists + Directory.CreateDirectory(aotAbiTempDir); + + string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.Android}-{abi}"); + + ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir); + + // The Godot exporter expects us to pass the abi in the tags parameter + exporter.AddSharedObject(soFilePath, tags: new[] { abi }); + } + } + } + + public static void CompileAssembliesForDesktop(ExportPlugin exporter, string platform, bool isDebug, string bits, AotOptions aotOpts, string aotTempDir, string outputDataDir, IDictionary<string, string> assemblies, string bclDir) + { + foreach (var assembly in assemblies) + { + string assemblyName = assembly.Key; + string assemblyPath = assembly.Value; + + string outputFileExtension = platform == OS.Platforms.Windows ? ".dll" : + platform == OS.Platforms.OSX ? ".dylib" : + ".so"; + + string outputFileName = assemblyName + ".dll" + outputFileExtension; + string tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); + + var compilerArgs = GetAotCompilerArgs(platform, isDebug, bits, aotOpts, assemblyPath, tempOutputFilePath); + + string compilerDirPath = GetMonoCrossDesktopDirName(platform, bits); + + ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir); + + if (platform == OS.Platforms.OSX) + { + exporter.AddSharedObject(tempOutputFilePath, tags: null); + } + else + { + string outputDataLibDir = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib"); + File.Copy(tempOutputFilePath, Path.Combine(outputDataLibDir, outputFileName)); + } + } + } + + public static void CompileAssembliesForiOS(ExportPlugin exporter, bool isDebug, string[] architectures, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir) + { + var cppCode = new StringBuilder(); + var aotModuleInfoSymbols = new List<string>(assemblies.Count); + + // {arch: paths} + var objFilePathsForiOSArch = architectures.ToDictionary(arch => arch, arch => new List<string>(assemblies.Count)); + + foreach (var assembly in assemblies) + { + string assemblyName = assembly.Key; + string assemblyPath = assembly.Value; + + string asmFileName = assemblyName + ".dll.S"; + string objFileName = assemblyName + ".dll.o"; + + foreach (string arch in architectures) + { + string aotArchTempDir = Path.Combine(aotTempDir, arch); + string asmFilePath = Path.Combine(aotArchTempDir, asmFileName); + + var compilerArgs = GetAotCompilerArgs(OS.Platforms.iOS, isDebug, arch, aotOpts, assemblyPath, asmFilePath); + + // Make sure the output directory exists + Directory.CreateDirectory(aotArchTempDir); + + string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.iOS}-{arch}"); + + ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir); + + // Assembling + bool isSim = arch == "i386" || arch == "x86_64"; // Shouldn't really happen as we don't do AOT for the simulator + string versionMinName = isSim ? "iphonesimulator" : "iphoneos"; + string iOSPlatformName = isSim ? "iPhoneSimulator" : "iPhoneOS"; + const string versionMin = "10.0"; // TODO: Turn this hard-coded version into an exporter setting + string iOSSdkPath = Path.Combine(XcodeHelper.XcodePath, + $"Contents/Developer/Platforms/{iOSPlatformName}.platform/Developer/SDKs/{iOSPlatformName}.sdk"); + + string objFilePath = Path.Combine(aotArchTempDir, objFileName); + + var clangArgs = new List<string>() + { + "-isysroot", iOSSdkPath, + "-Qunused-arguments", + $"-m{versionMinName}-version-min={versionMin}", + "-arch", arch, + "-c", + "-o", objFilePath, + "-x", "assembler" + }; + + if (isDebug) + clangArgs.Add("-DDEBUG"); + + clangArgs.Add(asmFilePath); + + int clangExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("clang"), clangArgs); + if (clangExitCode != 0) + throw new Exception($"Command 'clang' exited with code: {clangExitCode}"); + + objFilePathsForiOSArch[arch].Add(objFilePath); + } + + aotModuleInfoSymbols.Add($"mono_aot_module_{AssemblyNameToAotSymbol(assemblyName)}_info"); + } + + // Generate driver code + cppCode.AppendLine("#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)"); + cppCode.AppendLine("#define IOS_DEVICE"); + cppCode.AppendLine("#endif"); + + cppCode.AppendLine("#ifdef IOS_DEVICE"); + cppCode.AppendLine("extern \"C\" {"); + cppCode.AppendLine("// Mono API"); + cppCode.AppendLine(@" +typedef enum { +MONO_AOT_MODE_NONE, +MONO_AOT_MODE_NORMAL, +MONO_AOT_MODE_HYBRID, +MONO_AOT_MODE_FULL, +MONO_AOT_MODE_LLVMONLY, +MONO_AOT_MODE_INTERP, +MONO_AOT_MODE_INTERP_LLVMONLY, +MONO_AOT_MODE_LLVMONLY_INTERP, +MONO_AOT_MODE_LAST = 1000, +} MonoAotMode;"); + cppCode.AppendLine("void mono_jit_set_aot_mode(MonoAotMode);"); + cppCode.AppendLine("void mono_aot_register_module(void *);"); + + if (aotOpts.UseInterpreter) + { + cppCode.AppendLine("void mono_ee_interp_init(const char *);"); + cppCode.AppendLine("void mono_icall_table_init();"); + cppCode.AppendLine("void mono_marshal_ilgen_init();"); + cppCode.AppendLine("void mono_method_builder_ilgen_init();"); + cppCode.AppendLine("void mono_sgen_mono_ilgen_init();"); + } + + foreach (string symbol in aotModuleInfoSymbols) + cppCode.AppendLine($"extern void *{symbol};"); + + cppCode.AppendLine("void gd_mono_setup_aot() {"); + + foreach (string symbol in aotModuleInfoSymbols) + cppCode.AppendLine($"\tmono_aot_register_module({symbol});"); + + if (aotOpts.UseInterpreter) + { + cppCode.AppendLine("\tmono_icall_table_init();"); + cppCode.AppendLine("\tmono_marshal_ilgen_init();"); + cppCode.AppendLine("\tmono_method_builder_ilgen_init();"); + cppCode.AppendLine("\tmono_sgen_mono_ilgen_init();"); + cppCode.AppendLine("\tmono_ee_interp_init(0);"); + } + + string aotModeStr = null; + + if (aotOpts.LLVMOnly) + { + aotModeStr = "MONO_AOT_MODE_LLVMONLY"; // --aot=llvmonly + } + else + { + if (aotOpts.UseInterpreter) + aotModeStr = "MONO_AOT_MODE_INTERP"; // --aot=interp or --aot=interp,full + else if (aotOpts.FullAot) + aotModeStr = "MONO_AOT_MODE_FULL"; // --aot=full + } + + // One of the options above is always set for iOS + Debug.Assert(aotModeStr != null); + + cppCode.AppendLine($"\tmono_jit_set_aot_mode({aotModeStr});"); + + cppCode.AppendLine("} // gd_mono_setup_aot"); + cppCode.AppendLine("} // extern \"C\""); + cppCode.AppendLine("#endif // IOS_DEVICE"); + + // Add the driver code to the Xcode project + exporter.AddIosCppCode(cppCode.ToString()); + + // Archive the AOT object files into a static library + + var arFilePathsForAllArchs = new List<string>(); + string projectAssemblyName = GodotSharpEditor.ProjectAssemblyName; + + foreach (var archPathsPair in objFilePathsForiOSArch) + { + string arch = archPathsPair.Key; + var objFilePaths = archPathsPair.Value; + + string arOutputFilePath = Path.Combine(aotTempDir, $"lib-aot-{projectAssemblyName}.{arch}.a"); + + var arArgs = new List<string>() + { + "cr", + arOutputFilePath + }; + + foreach (string objFilePath in objFilePaths) + arArgs.Add(objFilePath); + + int arExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("ar"), arArgs); + if (arExitCode != 0) + throw new Exception($"Command 'ar' exited with code: {arExitCode}"); + + arFilePathsForAllArchs.Add(arOutputFilePath); + } + + // It's lipo time + + string fatOutputFileName = $"lib-aot-{projectAssemblyName}.fat.a"; + string fatOutputFilePath = Path.Combine(aotTempDir, fatOutputFileName); + + var lipoArgs = new List<string>(); + lipoArgs.Add("-create"); + lipoArgs.AddRange(arFilePathsForAllArchs); + lipoArgs.Add("-output"); + lipoArgs.Add(fatOutputFilePath); + + int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs); + if (lipoExitCode != 0) + throw new Exception($"Command 'lipo' exited with code: {lipoExitCode}"); + + // TODO: Add the AOT lib and interpreter libs as device only to supress warnings when targeting the simulator + + // Add the fat AOT static library to the Xcode project + exporter.AddIosProjectStaticLib(fatOutputFilePath); + + // Add the required Mono libraries to the Xcode project + + string MonoLibFile(string libFileName) => libFileName + ".iphone.fat.a"; + + string MonoLibFromTemplate(string libFileName) => + Path.Combine(Internal.FullTemplatesDir, "iphone-mono-libs", MonoLibFile(libFileName)); + + exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmonosgen-2.0")); + + exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-native")); + + if (aotOpts.UseInterpreter) + { + exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ee-interp")); + exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-icall-table")); + exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ilgen")); + } + + // TODO: Turn into an exporter option + bool enableProfiling = false; + if (enableProfiling) + exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-profiler-log")); + + // Add frameworks required by Mono to the Xcode project + exporter.AddIosFramework("libiconv.tbd"); + exporter.AddIosFramework("GSS.framework"); + exporter.AddIosFramework("CFNetwork.framework"); + + // Force load and export dynamic are needed for the linker to not strip required symbols. + // In theory we shouldn't be relying on this for P/Invoked functions (as is the case with + // functions in System.Native/libmono-native). Instead, we should use cecil to search for + // DllImports in assemblies and pass them to 'ld' as '-u/--undefined {pinvoke_symbol}'. + exporter.AddIosLinkerFlags("-rdynamic"); + exporter.AddIosLinkerFlags($"-force_load \"$(SRCROOT)/{MonoLibFile("libmono-native")}\""); + } + + /// Converts an assembly name to a valid symbol name in the same way the AOT compiler does + private static string AssemblyNameToAotSymbol(string assemblyName) + { + var builder = new StringBuilder(); + + foreach (var charByte in Encoding.UTF8.GetBytes(assemblyName)) + { + char @char = (char)charByte; + builder.Append(Char.IsLetterOrDigit(@char) || @char == '_' ? @char : '_'); + } + + return builder.ToString(); + } + + private static IEnumerable<string> GetAotCompilerArgs(string platform, bool isDebug, string target, AotOptions aotOpts, string assemblyPath, string outputFilePath) + { + // TODO: LLVM + + bool aotSoftDebug = isDebug && !aotOpts.EnableLLVM; + bool aotDwarfDebug = platform == OS.Platforms.iOS; + + var aotOptions = new List<string>(); + var optimizerOptions = new List<string>(); + + if (aotOpts.LLVMOnly) + { + aotOptions.Add("llvmonly"); + } + else + { + // Can be both 'interp' and 'full' + if (aotOpts.UseInterpreter) + aotOptions.Add("interp"); + if (aotOpts.FullAot) + aotOptions.Add("full"); + } + + aotOptions.Add(aotSoftDebug ? "soft-debug" : "nodebug"); + + if (aotDwarfDebug) + aotOptions.Add("dwarfdebug"); + + if (platform == OS.Platforms.Android) + { + string abi = target; + + string androidToolchain = aotOpts.ToolchainPath; + + if (string.IsNullOrEmpty(androidToolchain)) + { + androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}" + + if (!Directory.Exists(androidToolchain)) + throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings."); + } + else if (!Directory.Exists(androidToolchain)) + { + throw new FileNotFoundException("Android toolchain not found: " + androidToolchain); + } + + var androidToolPrefixes = new Dictionary<string, string> + { + ["armeabi-v7a"] = "arm-linux-androideabi-", + ["arm64-v8a"] = "aarch64-linux-android-", + ["x86"] = "i686-linux-android-", + ["x86_64"] = "x86_64-linux-android-" + }; + + aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi])); + + string triple = GetAndroidTriple(abi); + aotOptions.Add($"mtriple={triple}"); + } + else if (platform == OS.Platforms.iOS) + { + if (!aotOpts.LLVMOnly && !aotOpts.UseInterpreter) + optimizerOptions.Add("gsharedvt"); + + aotOptions.Add("static"); + + // I couldn't get the Mono cross-compiler to do assembling, so we'll have to do it ourselves + aotOptions.Add("asmonly"); + + aotOptions.Add("direct-icalls"); + + if (aotSoftDebug) + aotOptions.Add("no-direct-calls"); + + if (aotOpts.LLVMOnly || !aotOpts.UseInterpreter) + aotOptions.Add("direct-pinvoke"); + + string arch = target; + aotOptions.Add($"mtriple={arch}-ios"); + } + + aotOptions.Add($"outfile={outputFilePath}"); + + if (aotOpts.EnableLLVM) + { + aotOptions.Add($"llvm-path={aotOpts.LLVMPath}"); + aotOptions.Add($"llvm-outfile={aotOpts.LLVMOutputPath}"); + } + + if (aotOpts.ExtraAotOptions.Length > 0) + aotOptions.AddRange(aotOpts.ExtraAotOptions); + + if (aotOpts.ExtraOptimizerOptions.Length > 0) + optimizerOptions.AddRange(aotOpts.ExtraOptimizerOptions); + + string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option; + string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption)); + + var runtimeArgs = new List<string>(); + + // The '--debug' runtime option is required when using the 'soft-debug' and 'dwarfdebug' AOT options + if (aotSoftDebug || aotDwarfDebug) + runtimeArgs.Add("--debug"); + + if (aotOpts.EnableLLVM) + runtimeArgs.Add("--llvm"); + + runtimeArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot"); + + if (optimizerOptions.Count > 0) + runtimeArgs.Add($"-O={OptionsToString(optimizerOptions)}"); + + runtimeArgs.Add(assemblyPath); + + return runtimeArgs; + } + + private static void ExecuteCompiler(string compiler, IEnumerable<string> compilerArgs, string bclDir) + { + // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead + string CmdLineArgsToString(IEnumerable<string> args) + { + // Not perfect, but as long as we are careful... + return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); + } + + using (var process = new Process()) + { + process.StartInfo = new ProcessStartInfo(compiler, CmdLineArgsToString(compilerArgs)) + { + UseShellExecute = false + }; + + process.StartInfo.EnvironmentVariables.Remove("MONO_ENV_OPTIONS"); + process.StartInfo.EnvironmentVariables.Remove("MONO_THREADS_SUSPEND"); + process.StartInfo.EnvironmentVariables.Add("MONO_PATH", bclDir); + + Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}"); + + if (!process.Start()) + throw new Exception("Failed to start process for Mono AOT compiler"); + + process.WaitForExit(); + + if (process.ExitCode != 0) + throw new Exception($"Mono AOT compiler exited with code: {process.ExitCode}"); + } + } + + private static IEnumerable<string> GetEnablediOSArchs(string[] features) + { + var iosArchs = new[] + { + "armv7", + "arm64" + }; + + return iosArchs.Where(features.Contains); + } + + private static IEnumerable<string> GetEnabledAndroidAbis(string[] features) + { + var androidAbis = new[] + { + "armeabi-v7a", + "arm64-v8a", + "x86", + "x86_64" + }; + + return androidAbis.Where(features.Contains); + } + + private static string GetAndroidTriple(string abi) + { + var abiArchs = new Dictionary<string, string> + { + ["armeabi-v7a"] = "armv7", + ["arm64-v8a"] = "aarch64-v8a", + ["x86"] = "i686", + ["x86_64"] = "x86_64" + }; + + string arch = abiArchs[abi]; + + return $"{arch}-linux-android"; + } + + private static string GetMonoCrossDesktopDirName(string platform, string bits) + { + switch (platform) + { + case OS.Platforms.Windows: + case OS.Platforms.UWP: + { + string arch = bits == "64" ? "x86_64" : "i686"; + return $"windows-{arch}"; + } + case OS.Platforms.OSX: + { + Debug.Assert(bits == null || bits == "64"); + string arch = "x86_64"; + return $"{platform}-{arch}"; + } + case OS.Platforms.X11: + case OS.Platforms.Server: + { + string arch = bits == "64" ? "x86_64" : "i686"; + return $"linux-{arch}"; + } + case OS.Platforms.Haiku: + { + string arch = bits == "64" ? "x86_64" : "i686"; + return $"{platform}-{arch}"; + } + default: + throw new NotSupportedException($"Platform not supported: {platform}"); + } + } + + // TODO: Replace this for a specific path for each platform + private static string FindCrossCompiler(string monoCrossBin) + { + string exeExt = OS.IsWindows ? ".exe" : string.Empty; + + var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}", SearchOption.TopDirectoryOnly); + if (files.Length > 0) + return Path.Combine(monoCrossBin, files[0].Name); + + throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}"); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 3e2a8c22a9..d782d4e61b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -29,15 +29,13 @@ namespace GodotTools.Export All = CJK | MidEast | Other | Rare | West } - private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string platform) + private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir) { - var codesets = (I18NCodesets) ProjectSettings.GetSetting("mono/export/i18n_codesets"); + var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets"); if (codesets == I18NCodesets.None) return; - string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir(); - void AddI18NAssembly(string name) => assemblies.Add(name, Path.Combine(bclDir, $"{name}.dll")); AddI18NAssembly("I18N"); @@ -73,6 +71,7 @@ namespace GodotTools.Export GlobalDef("mono/export/aot/enabled", false); GlobalDef("mono/export/aot/full_aot", false); + GlobalDef("mono/export/aot/use_interpreter", true); // --aot or --aot=opt1,opt2 (use 'mono --aot=help AuxAssembly.dll' to list AOT options) GlobalDef("mono/export/aot/extra_aot_options", new string[] { }); @@ -86,9 +85,11 @@ namespace GodotTools.Export private void AddFile(string srcPath, string dstPath, bool remap = false) { + // Add file to the PCK AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); } + // With this method we can override how a file is exported in the PCK public override void _ExportFile(string path, string type, string[] features) { base._ExportFile(path, type, features); @@ -96,7 +97,7 @@ namespace GodotTools.Export if (type != Internal.CSharpLanguageType) return; - if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}") + if (Path.GetExtension(path) != Internal.CSharpLanguageExtension) throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); // TODO What if the source file is not part of the game's C# project @@ -110,6 +111,8 @@ namespace GodotTools.Export // Sadly, Godot prints errors when adding an empty file (nothing goes wrong, it's just noise). // Because of this, we add a file which contains a line break. AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false); + + // Tell the Godot exporter that we already took care of the file Skip(); } } @@ -150,7 +153,7 @@ namespace GodotTools.Export string outputDir = new FileInfo(path).Directory?.FullName ?? throw new FileNotFoundException("Base directory not found"); - string buildConfig = isDebug ? "Debug" : "Release"; + string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); @@ -167,12 +170,7 @@ namespace GodotTools.Export var dependencies = new Godot.Collections.Dictionary<string, string>(); - var projectDllName = (string)ProjectSettings.GetSetting("application/config/name"); - if (projectDllName.Empty()) - { - projectDllName = "UnnamedProject"; - } - + string projectDllName = GodotSharpEditor.ProjectAssemblyName; string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); @@ -189,10 +187,12 @@ namespace GodotTools.Export dependencies["Mono.Android"] = monoAndroidAssemblyPath; } + string bclDir = DeterminePlatformBclDir(platform); + var initialDependencies = dependencies.Duplicate(); - internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies); + internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, bclDir, dependencies); - AddI18NAssemblies(dependencies, platform); + AddI18NAssemblies(dependencies, bclDir); string outputDataDir = null; @@ -227,11 +227,34 @@ namespace GodotTools.Export } } - // AOT + // AOT compilation + bool aotEnabled = platform == OS.Platforms.iOS || (bool)ProjectSettings.GetSetting("mono/export/aot/enabled"); - if ((bool)ProjectSettings.GetSetting("mono/export/aot/enabled")) + if (aotEnabled) { - AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies); + string aotToolchainPath = null; + + if (platform == OS.Platforms.Android) + aotToolchainPath = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path"); + + if (aotToolchainPath == string.Empty) + aotToolchainPath = null; // Don't risk it being used as current working dir + + // TODO: LLVM settings are hard-coded and disabled for now + var aotOpts = new AotOptions + { + EnableLLVM = false, + LLVMOnly = false, + LLVMPath = "", + LLVMOutputPath = "", + FullAot = platform == OS.Platforms.iOS || (bool)(ProjectSettings.GetSetting("mono/export/aot/full_aot") ?? false), + UseInterpreter = (bool)ProjectSettings.GetSetting("mono/export/aot/use_interpreter"), + ExtraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options") ?? new string[] { }, + ExtraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options") ?? new string[] { }, + ToolchainPath = aotToolchainPath + }; + + AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir, outputDataDir, dependencies); } } @@ -258,7 +281,8 @@ namespace GodotTools.Export { string target = isDebug ? "release_debug" : "release"; - // NOTE: Bits is ok for now as all platforms with a data directory have it, but that may change in the future. + // NOTE: Bits is ok for now as all platforms with a data directory only have one or two architectures. + // However, this may change in the future if we add arm linux or windows desktop templates. string bits = features.Contains("64") ? "64" : "32"; string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}"; @@ -284,7 +308,7 @@ namespace GodotTools.Export if (!validTemplatePathFound) throw new FileNotFoundException("Data template directory not found", templateDirPath); - string outputDataDir = Path.Combine(outputDir, DataDirName); + string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject()); if (Directory.Exists(outputDataDir)) Directory.Delete(outputDataDir, recursive: true); // Clean first @@ -304,333 +328,10 @@ namespace GodotTools.Export return outputDataDir; } - private void AotCompileDependencies(string[] features, string platform, bool isDebug, string outputDir, string outputDataDir, IDictionary<string, string> dependencies) - { - // TODO: WASM - - string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir(); - - string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}"); - - if (!Directory.Exists(aotTempDir)) - Directory.CreateDirectory(aotTempDir); - - var assemblies = new Dictionary<string, string>(); - - foreach (var dependency in dependencies) - { - string assemblyName = dependency.Key; - string assemblyPath = dependency.Value; - - string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll"); - - if (File.Exists(assemblyPathInBcl)) - { - // Don't create teporaries for assemblies from the BCL - assemblies.Add(assemblyName, assemblyPathInBcl); - } - else - { - string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll"); - File.Copy(assemblyPath, tempAssemblyPath); - assemblies.Add(assemblyName, tempAssemblyPath); - } - } - - foreach (var assembly in assemblies) - { - string assemblyName = assembly.Key; - string assemblyPath = assembly.Value; - - string sharedLibExtension = platform == OS.Platforms.Windows ? ".dll" : - platform == OS.Platforms.OSX ? ".dylib" : - platform == OS.Platforms.HTML5 ? ".wasm" : - ".so"; - - string outputFileName = assemblyName + ".dll" + sharedLibExtension; - - if (platform == OS.Platforms.Android) - { - // Not sure if the 'lib' prefix is an Android thing or just Godot being picky, - // but we use '-aot-' as well just in case to avoid conflicts with other libs. - outputFileName = "lib-aot-" + outputFileName; - } - - string outputFilePath = null; - string tempOutputFilePath; - - switch (platform) - { - case OS.Platforms.OSX: - tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); - break; - case OS.Platforms.Android: - tempOutputFilePath = Path.Combine(aotTempDir, "%%ANDROID_ABI%%", outputFileName); - break; - case OS.Platforms.HTML5: - tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); - outputFilePath = Path.Combine(outputDir, outputFileName); - break; - default: - tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); - outputFilePath = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib", outputFileName); - break; - } - - var data = new Dictionary<string, string>(); - var enabledAndroidAbis = platform == OS.Platforms.Android ? GetEnabledAndroidAbis(features).ToArray() : null; - - if (platform == OS.Platforms.Android) - { - Debug.Assert(enabledAndroidAbis != null); - - foreach (var abi in enabledAndroidAbis) - { - data["abi"] = abi; - var outputFilePathForThisAbi = tempOutputFilePath.Replace("%%ANDROID_ABI%%", abi); - - AotCompileAssembly(platform, isDebug, data, assemblyPath, outputFilePathForThisAbi); - - AddSharedObject(outputFilePathForThisAbi, tags: new[] { abi }); - } - } - else - { - string bits = features.Contains("64") ? "64" : features.Contains("64") ? "32" : null; - - if (bits != null) - data["bits"] = bits; - - AotCompileAssembly(platform, isDebug, data, assemblyPath, tempOutputFilePath); - - if (platform == OS.Platforms.OSX) - { - AddSharedObject(tempOutputFilePath, tags: null); - } - else - { - Debug.Assert(outputFilePath != null); - File.Copy(tempOutputFilePath, outputFilePath); - } - } - } - } - - private static void AotCompileAssembly(string platform, bool isDebug, Dictionary<string, string> data, string assemblyPath, string outputFilePath) - { - // Make sure the output directory exists - Directory.CreateDirectory(outputFilePath.GetBaseDir()); - - string exeExt = OS.IsWindows ? ".exe" : string.Empty; - - string monoCrossDirName = DetermineMonoCrossDirName(platform, data); - string monoCrossRoot = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", monoCrossDirName); - string monoCrossBin = Path.Combine(monoCrossRoot, "bin"); - - string toolPrefix = DetermineToolPrefix(monoCrossBin); - string monoExeName = System.IO.File.Exists(Path.Combine(monoCrossBin, $"{toolPrefix}mono{exeExt}")) ? "mono" : "mono-sgen"; - - string compilerCommand = Path.Combine(monoCrossBin, $"{toolPrefix}{monoExeName}{exeExt}"); - - bool fullAot = (bool)ProjectSettings.GetSetting("mono/export/aot/full_aot"); - - string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option; - string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption)); - - var aotOptions = new List<string>(); - var optimizerOptions = new List<string>(); - - if (fullAot) - aotOptions.Add("full"); - - aotOptions.Add(isDebug ? "soft-debug" : "nodebug"); - - if (platform == OS.Platforms.Android) - { - string abi = data["abi"]; - - string androidToolchain = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path"); - - if (string.IsNullOrEmpty(androidToolchain)) - { - androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}" - - if (!Directory.Exists(androidToolchain)) - throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings."); - } - else if (!Directory.Exists(androidToolchain)) - { - throw new FileNotFoundException("Android toolchain not found: " + androidToolchain); - } - - var androidToolPrefixes = new Dictionary<string, string> - { - ["armeabi-v7a"] = "arm-linux-androideabi-", - ["arm64-v8a"] = "aarch64-linux-android-", - ["x86"] = "i686-linux-android-", - ["x86_64"] = "x86_64-linux-android-" - }; - - aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi])); - - string triple = GetAndroidTriple(abi); - aotOptions.Add($"mtriple={triple}"); - } - - aotOptions.Add($"outfile={outputFilePath}"); - - var extraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options"); - var extraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options"); - - if (extraAotOptions.Length > 0) - aotOptions.AddRange(extraAotOptions); - - if (extraOptimizerOptions.Length > 0) - optimizerOptions.AddRange(extraOptimizerOptions); - - var compilerArgs = new List<string>(); - - if (isDebug) - compilerArgs.Add("--debug"); // Required for --aot=soft-debug - - compilerArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot"); - - if (optimizerOptions.Count > 0) - compilerArgs.Add($"-O={OptionsToString(optimizerOptions)}"); - - compilerArgs.Add(ProjectSettings.GlobalizePath(assemblyPath)); - - // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead - string CmdLineArgsToString(IEnumerable<string> args) - { - // Not perfect, but as long as we are careful... - return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); - } - - using (var process = new Process()) - { - process.StartInfo = new ProcessStartInfo(compilerCommand, CmdLineArgsToString(compilerArgs)) - { - UseShellExecute = false - }; - - string platformBclDir = DeterminePlatformBclDir(platform); - process.StartInfo.EnvironmentVariables.Add("MONO_PATH", string.IsNullOrEmpty(platformBclDir) ? - typeof(object).Assembly.Location.GetBaseDir() : - platformBclDir); - - Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}"); - - if (!process.Start()) - throw new Exception("Failed to start process for Mono AOT compiler"); - - process.WaitForExit(); - - if (process.ExitCode != 0) - throw new Exception($"Mono AOT compiler exited with error code: {process.ExitCode}"); - - if (!System.IO.File.Exists(outputFilePath)) - throw new Exception("Mono AOT compiler finished successfully but the output file is missing"); - } - } - - private static string DetermineMonoCrossDirName(string platform, IReadOnlyDictionary<string, string> data) - { - switch (platform) - { - case OS.Platforms.Windows: - case OS.Platforms.UWP: - { - string arch = data["bits"] == "64" ? "x86_64" : "i686"; - return $"windows-{arch}"; - } - case OS.Platforms.OSX: - { - string arch = "x86_64"; - return $"{platform}-{arch}"; - } - case OS.Platforms.X11: - case OS.Platforms.Server: - { - string arch = data["bits"] == "64" ? "x86_64" : "i686"; - return $"linux-{arch}"; - } - case OS.Platforms.Haiku: - { - string arch = data["bits"] == "64" ? "x86_64" : "i686"; - return $"{platform}-{arch}"; - } - case OS.Platforms.Android: - { - string abi = data["abi"]; - return $"{platform}-{abi}"; - } - case OS.Platforms.HTML5: - return "wasm-wasm32"; - default: - throw new NotSupportedException($"Platform not supported: {platform}"); - } - } - - private static string DetermineToolPrefix(string monoCrossBin) - { - string exeExt = OS.IsWindows ? ".exe" : string.Empty; - - if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono{exeExt}"))) - return string.Empty; - - if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono-sgen{exeExt}" + exeExt))) - return string.Empty; - - var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono{exeExt}" + exeExt, SearchOption.TopDirectoryOnly); - if (files.Length > 0) - { - string fileName = files[0].Name; - return fileName.Substring(0, fileName.Length - $"mono{exeExt}".Length); - } - - files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}" + exeExt, SearchOption.TopDirectoryOnly); - if (files.Length > 0) - { - string fileName = files[0].Name; - return fileName.Substring(0, fileName.Length - $"mono-sgen{exeExt}".Length); - } - - throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}"); - } - - private static IEnumerable<string> GetEnabledAndroidAbis(string[] features) - { - var androidAbis = new[] - { - "armeabi-v7a", - "arm64-v8a", - "x86", - "x86_64" - }; - - return androidAbis.Where(features.Contains); - } - - private static string GetAndroidTriple(string abi) - { - var abiArchs = new Dictionary<string, string> - { - ["armeabi-v7a"] = "armv7", - ["arm64-v8a"] = "aarch64-v8a", - ["x86"] = "i686", - ["x86_64"] = "x86_64" - }; - - string arch = abiArchs[abi]; - - return $"{arch}-linux-android"; - } - private static bool PlatformHasTemplateDir(string platform) { // OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest. - return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.HTML5 }.Contains(platform); + return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform); } private static string DeterminePlatformFromFeatures(IEnumerable<string> features) @@ -665,7 +366,7 @@ namespace GodotTools.Export if (PlatformRequiresCustomBcl(platform)) throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}"); - platformBclDir = null; // Use the one we're running on + platformBclDir = typeof(object).Assembly.Location; // Use the one we're running on } } @@ -678,7 +379,7 @@ namespace GodotTools.Export /// </summary> private static bool PlatformRequiresCustomBcl(string platform) { - if (new[] { OS.Platforms.Android, OS.Platforms.HTML5 }.Contains(platform)) + if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform)) return true; // The 'net_4_x' BCL is not compatible between Windows and the other platforms. @@ -707,6 +408,8 @@ namespace GodotTools.Export return "net_4_x"; case OS.Platforms.Android: return "monodroid"; + case OS.Platforms.iOS: + return "monotouch"; case OS.Platforms.HTML5: return "wasm"; default: @@ -714,14 +417,11 @@ namespace GodotTools.Export } } - private static string DataDirName + private static string DetermineDataDirNameForProject() { - get - { - var appName = (string)ProjectSettings.GetSetting("application/config/name"); - string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); - return $"data_{appNameSafe}"; - } + var appName = (string)ProjectSettings.GetSetting("application/config/name"); + string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); + return $"data_{appNameSafe}"; } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs new file mode 100755 index 0000000000..219b7a698a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; + +namespace GodotTools.Export +{ + public static class XcodeHelper + { + private static string _XcodePath = null; + + public static string XcodePath + { + get + { + if (_XcodePath == null) + { + _XcodePath = FindXcode(); + + if (_XcodePath == null) + throw new Exception("Could not find Xcode"); + } + + return _XcodePath; + } + } + + private static string FindSelectedXcode() + { + var outputWrapper = new Godot.Collections.Array(); + + int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, blocking: true, output: outputWrapper); + + if (exitCode == 0) + { + string output = (string)outputWrapper[0]; + return output.Trim(); + } + + Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}"); + + return null; + } + + public static string FindXcode() + { + string selectedXcode = FindSelectedXcode(); + if (selectedXcode != null) + { + if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer"))) + return selectedXcode; + + // The path already pointed to Contents/Developer + var dirInfo = new DirectoryInfo(selectedXcode); + if (dirInfo.Name != "Developer" || dirInfo.Parent.Name != "Contents") + { + Console.WriteLine(Path.GetDirectoryName(selectedXcode)); + Console.WriteLine(System.IO.Directory.GetParent(selectedXcode).Name); + Console.Error.WriteLine("Unrecognized path for selected Xcode"); + } + else + { + return System.IO.Path.GetFullPath($"{selectedXcode}/../.."); + } + } + else + { + Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path"); + } + + const string XcodeHintPath = "/Applications/Xcode.app"; + + if (Directory.Exists(XcodeHintPath)) + { + if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer"))) + return XcodeHintPath; + + Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory"); + } + + return null; + } + + public static string FindXcodeTool(string toolName) + { + string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain"); + + string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName); + if (File.Exists(path)) + return path; + + throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}"); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 147bc95bb8..c9d7dd26f8 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -13,6 +13,7 @@ using JetBrains.Annotations; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; using OS = GodotTools.Utils.OS; +using Path = System.IO.Path; namespace GodotTools { @@ -35,6 +36,17 @@ namespace GodotTools public BottomPanel BottomPanel { get; private set; } + public static string ProjectAssemblyName + { + get + { + var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); + if (string.IsNullOrEmpty(projectAssemblyName)) + projectAssemblyName = "UnnamedProject"; + return projectAssemblyName; + } + } + private bool CreateProjectSolution() { using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3)) @@ -44,9 +56,7 @@ namespace GodotTools string resourceDir = ProjectSettings.GlobalizePath("res://"); string path = resourceDir; - string name = (string)ProjectSettings.GetSetting("application/config/name"); - if (name.Empty()) - name = "UnnamedProject"; + string name = ProjectAssemblyName; string guid = CsProjOperations.GenerateGameProject(path, name); @@ -61,7 +71,7 @@ namespace GodotTools { Guid = guid, PathRelativeToSolution = name + ".csproj", - Configs = new List<string> { "Debug", "Release", "Tools" } + Configs = new List<string> {"Debug", "ExportDebug", "ExportRelease"} }; solution.AddNewProject(name, projectInfo); @@ -118,19 +128,12 @@ namespace GodotTools { 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); + aboutDialog.PopupCentered(); } - private void _MenuOptionPressed(MenuOptions id) + private void _MenuOptionPressed(int id) { - switch (id) + switch ((MenuOptions)id) { case MenuOptions.CreateSln: CreateProjectSolution(); @@ -163,11 +166,41 @@ namespace GodotTools bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); if (showInfoDialog) { - aboutDialog.PopupExclusive = true; + aboutDialog.Exclusive = true; _ShowAboutDialog(); // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on. - aboutDialog.PopupExclusive = false; + aboutDialog.Exclusive = false; } + + var fileSystemDock = GetEditorInterface().GetFileSystemDock(); + + fileSystemDock.FilesMoved += (file, newFile) => + { + if (Path.GetExtension(file) == Internal.CSharpLanguageExtension) + { + ProjectUtils.RenameItemInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile", + ProjectSettings.GlobalizePath(file), ProjectSettings.GlobalizePath(newFile)); + } + }; + + fileSystemDock.FileRemoved += file => + { + if (Path.GetExtension(file) == Internal.CSharpLanguageExtension) + ProjectUtils.RemoveItemFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile", + ProjectSettings.GlobalizePath(file)); + }; + + fileSystemDock.FolderMoved += (oldFolder, newFolder) => + { + ProjectUtils.RenameItemsToNewFolderInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile", + ProjectSettings.GlobalizePath(oldFolder), ProjectSettings.GlobalizePath(newFolder)); + }; + + fileSystemDock.FolderRemoved += oldFolder => + { + ProjectUtils.RemoveItemsInFolderFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile", + ProjectSettings.GlobalizePath(oldFolder)); + }; } } @@ -179,9 +212,9 @@ namespace GodotTools public void ShowErrorDialog(string message, string title = "Error") { - errorDialog.WindowTitle = title; + errorDialog.Title = title; errorDialog.DialogText = message; - errorDialog.PopupCenteredMinsize(); + errorDialog.PopupCentered(); } private static string _vsCodePath = string.Empty; @@ -210,7 +243,7 @@ namespace GodotTools string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line); return Error.Ok; - } + } case ExternalEditorId.MonoDevelop: { string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); @@ -346,11 +379,10 @@ namespace GodotTools bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR()); - AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" }); + AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); menuPopup = new PopupMenu(); menuPopup.Hide(); - menuPopup.SetAsToplevel(true); AddToolSubmenuItem("Mono", menuPopup); @@ -359,7 +391,7 @@ namespace GodotTools menuPopup.AddItem("About C# support".TTR(), (int)MenuOptions.AboutCSharp); aboutDialog = new AcceptDialog(); editorBaseControl.AddChild(aboutDialog); - aboutDialog.WindowTitle = "Important: C# support is not feature-complete"; + aboutDialog.Title = "Important: C# support is not feature-complete"; // We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox // we'll add. Instead we add containers and a new autowrapped Label inside. @@ -373,7 +405,7 @@ namespace GodotTools aboutVBox.AddChild(aboutHBox); var aboutIcon = new TextureRect(); - aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons"); + aboutIcon.Texture = aboutIcon.GetThemeIcon("NodeWarning", "EditorIcons"); aboutHBox.AddChild(aboutIcon); var aboutLabel = new Label(); @@ -394,15 +426,34 @@ namespace GodotTools EditorDef("mono/editor/show_info_on_start", true); // CheckBox in main container - aboutDialogCheckBox = new CheckBox { Text = "Show this warning when starting the editor" }; - aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart)); + aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"}; + aboutDialogCheckBox.Toggled += enabled => + { + bool showOnStart = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showOnStart != enabled) + editorSettings.SetSetting("mono/editor/show_info_on_start", enabled); + }; aboutVBox.AddChild(aboutDialogCheckBox); } if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { - // Make sure the existing project has Api assembly references configured correctly - CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); + try + { + // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease + DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath); + // Migrate csproj from old configuration names to: Debug, ExportDebug and ExportRelease + ProjectUtils.MigrateFromOldConfigNames(GodotSharpDirs.ProjectCsProjPath); + + // Apply the other fixes after configurations are migrated + + // Make sure the existing project has Api assembly references configured correctly + ProjectUtils.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); + } + catch (Exception e) + { + GD.PushError(e.ToString()); + } } else { @@ -410,7 +461,7 @@ namespace GodotTools menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln); } - menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed)); + menuPopup.IdPressed += _MenuOptionPressed; var buildButton = new ToolButton { @@ -418,7 +469,7 @@ namespace GodotTools HintTooltip = "Build solution", FocusMode = Control.FocusModeEnum.None }; - buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed)); + buildButton.PressedSignal += _BuildSolutionPressed; AddControlToContainer(CustomControlContainer.Toolbar, buildButton); // External editor settings diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 379dfd9f7d..ac9379adf8 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -51,7 +51,9 @@ </ItemGroup> <ItemGroup> <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="Export\AotBuilder.cs" /> <Compile Include="Export\ExportPlugin.cs" /> + <Compile Include="Export\XcodeHelper.cs" /> <Compile Include="ExternalEditorId.cs" /> <Compile Include="Ides\GodotIdeManager.cs" /> <Compile Include="Ides\GodotIdeServer.cs" /> diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index 0ed567afd1..ae05710f4f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -10,7 +10,7 @@ namespace GodotTools public override void _Notification(int what) { - if (what == MainLoop.NotificationWmFocusIn) + if (what == Node.NotificationWmFocusIn) { RestartTimer(); @@ -40,7 +40,7 @@ namespace GodotTools OneShot = false, WaitTime = (float)EditorDef("mono/assembly_watch_interval_sec", 0.5) }; - watchTimer.Connect("timeout", this, nameof(TimerTimeout)); + watchTimer.Timeout += TimerTimeout; AddChild(watchTimer); watchTimer.Start(); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs index 77740f0e53..e3a4fa7b45 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -162,7 +162,7 @@ namespace GodotTools.Ides.Rider private static string GetToolboxRiderRootPath(string localAppData) { - var toolboxPath = Path.Combine(localAppData, @"JetBrains\Toolbox"); + var toolboxPath = Path.Combine(localAppData, @"JetBrains/Toolbox"); var settingsJson = Path.Combine(toolboxPath, ".settings.json"); if (File.Exists(settingsJson)) @@ -172,7 +172,7 @@ namespace GodotTools.Ides.Rider toolboxPath = path; } - var toolboxRiderRootPath = Path.Combine(toolboxPath, @"apps\Rider"); + var toolboxRiderRootPath = Path.Combine(toolboxPath, @"apps/Rider"); return toolboxRiderRootPath; } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index ee5677a6a8..16f91a0925 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -55,6 +55,11 @@ namespace GodotTools.Ides.Rider } } + public static bool IsExternalEditorSetToRider(EditorSettings editorSettings) + { + return editorSettings.HasSetting(EditorPathSettingName) && IsRider((string) editorSettings.GetSetting(EditorPathSettingName)); + } + public static bool IsRider(string path) { if (string.IsNullOrEmpty(path)) diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 2e121ba879..026a7db89c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -8,7 +8,7 @@ namespace GodotTools.Internals public static class Internal { public const string CSharpLanguageType = "CSharpScript"; - public const string CSharpLanguageExtension = "cs"; + public const string CSharpLanguageExtension = ".cs"; public static string UpdateApiAssembliesFromPrebuilt(string config) => internal_UpdateApiAssembliesFromPrebuilt(config); diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index 279e67b3eb..b057ac12c6 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using JetBrains.Annotations; namespace GodotTools.Utils { @@ -26,6 +27,7 @@ namespace GodotTools.Utils public const string UWP = "UWP"; public const string Haiku = "Haiku"; public const string Android = "Android"; + public const string iOS = "iOS"; public const string HTML5 = "HTML5"; } @@ -33,11 +35,12 @@ namespace GodotTools.Utils { public const string Windows = "windows"; public const string OSX = "osx"; - public const string X11 = "x11"; + public const string X11 = "linuxbsd"; public const string Server = "server"; public const string UWP = "uwp"; public const string Haiku = "haiku"; public const string Android = "android"; + public const string iOS = "iphone"; public const string HTML5 = "javascript"; } @@ -50,6 +53,7 @@ namespace GodotTools.Utils [Names.UWP] = Platforms.UWP, [Names.Haiku] = Platforms.Haiku, [Names.Android] = Platforms.Android, + [Names.iOS] = Platforms.iOS, [Names.HTML5] = Platforms.HTML5 }; @@ -65,6 +69,7 @@ namespace GodotTools.Utils private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP)); private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku)); private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android)); + private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS)); private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5)); public static bool IsWindows => _isWindows.Value || IsUWP; @@ -74,10 +79,11 @@ namespace GodotTools.Utils public static bool IsUWP => _isUWP.Value; public static bool IsHaiku => _isHaiku.Value; public static bool IsAndroid => _isAndroid.Value; + public static bool IsiOS => _isiOS.Value; public static bool IsHTML5 => _isHTML5.Value; private static bool? _isUnixCache; - private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android }; + private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS }; public static bool IsUnixLike() { @@ -91,12 +97,12 @@ namespace GodotTools.Utils public static char PathSep => IsWindows ? ';' : ':'; - public static string PathWhich(string name) + public static string PathWhich([NotNull] string name) { return IsWindows ? PathWhichWindows(name) : PathWhichUnix(name); } - private static string PathWhichWindows(string name) + private static string PathWhichWindows([NotNull] string name) { string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? new string[] { }; string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); @@ -121,7 +127,7 @@ namespace GodotTools.Utils select path + ext).FirstOrDefault(File.Exists); } - private static string PathWhichUnix(string name) + private static string PathWhichUnix([NotNull] string name) { string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); @@ -163,5 +169,33 @@ namespace GodotTools.Utils User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself } } + + public static int ExecuteCommand(string command, IEnumerable<string> arguments) + { + // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead + string CmdLineArgsToString(IEnumerable<string> args) + { + // Not perfect, but as long as we are careful... + return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); + } + + var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)); + + Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}"); + + // Print the output + startInfo.RedirectStandardOutput = false; + startInfo.RedirectStandardError = false; + + startInfo.UseShellExecute = false; + + using (var process = new Process { StartInfo = startInfo }) + { + process.Start(); + process.WaitForExit(); + + return process.ExitCode; + } + } } } diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 908c72c591..71bb8ff851 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -76,7 +76,7 @@ #define GLUE_HEADER_FILE "glue_header.h" #define ICALL_PREFIX "godot_icall_" #define SINGLETON_ICALL_SUFFIX "_get_singleton" -#define ICALL_GET_METHODBIND ICALL_PREFIX "Object_ClassDB_get_method" +#define ICALL_GET_METHODBIND "__ClassDB_get_method" #define C_LOCAL_RET "ret" #define C_LOCAL_VARARG_RET "vararg_ret" @@ -95,6 +95,10 @@ #define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL "::mono_string_from_godot" #define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type #define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" +#define C_METHOD_MANAGED_TO_CALLABLE C_NS_MONOMARSHAL "::managed_to_callable" +#define C_METHOD_MANAGED_FROM_CALLABLE C_NS_MONOMARSHAL "::callable_to_managed" +#define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL "::signal_info_to_callable" +#define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL "::callable_to_signal_info" #define BINDINGS_GENERATOR_VERSION UINT32_C(11) @@ -407,7 +411,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append("\"/>"); } else { // Try to find as global enum constant - const EnumInterface *target_ienum = NULL; + const EnumInterface *target_ienum = nullptr; for (const List<EnumInterface>::Element *E = global_enums.front(); E; E = E->next()) { target_ienum = &E->get(); @@ -445,7 +449,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append("\"/>"); } else { // Try to find as enum constant in the current class - const EnumInterface *target_ienum = NULL; + const EnumInterface *target_ienum = nullptr; for (const List<EnumInterface>::Element *E = target_itype->enums.front(); E; E = E->next()) { target_ienum = &E->get(); @@ -504,23 +508,23 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(tag); xml_output.append("</c>"); } else if (tag == "PackedByteArray") { - xml_output.append("<see cref=\"byte\"/>"); + xml_output.append("<see cref=\"T:byte[]\"/>"); } else if (tag == "PackedInt32Array") { - xml_output.append("<see cref=\"int\"/>"); + xml_output.append("<see cref=\"T:int[]\"/>"); + } else if (tag == "PackedInt64Array") { + xml_output.append("<see cref=\"T:long[]\"/>"); } else if (tag == "PackedFloat32Array") { -#ifdef REAL_T_IS_DOUBLE - xml_output.append("<see cref=\"double\"/>"); -#else - xml_output.append("<see cref=\"float\"/>"); -#endif + xml_output.append("<see cref=\"T:float[]\"/>"); + } else if (tag == "PackedFloat64Array") { + xml_output.append("<see cref=\"T:double[]\"/>"); } else if (tag == "PackedStringArray") { - xml_output.append("<see cref=\"string\"/>"); + xml_output.append("<see cref=\"T:string[]\"/>"); } else if (tag == "PackedVector2Array") { - xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>"); + xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector2[]\"/>"); } else if (tag == "PackedVector3Array") { - xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>"); + xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector3[]\"/>"); } else if (tag == "PackedColorArray") { - xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>"); + xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Color[]\"/>"); } else { const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag)); @@ -778,7 +782,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { const ConstantInterface &iconstant = E->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { @@ -839,7 +843,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { const ConstantInterface &iconstant = F->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { @@ -932,7 +936,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { "using System.Runtime.CompilerServices;\n" "\n"); cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); + cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 "{"); cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = "); cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); @@ -944,7 +948,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { #define ADD_INTERNAL_CALL(m_icall) \ if (!m_icall.editor_only) { \ cs_icalls_content.append(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.append(INDENT2 "internal extern static "); \ + cs_icalls_content.append(INDENT2 "internal static extern "); \ cs_icalls_content.append(m_icall.im_type_out + " "); \ cs_icalls_content.append(m_icall.name + "("); \ cs_icalls_content.append(m_icall.im_sig + ");\n"); \ @@ -1046,7 +1050,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) { #define ADD_INTERNAL_CALL(m_icall) \ if (m_icall.editor_only) { \ cs_icalls_content.append(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.append(INDENT2 "internal extern static "); \ + cs_icalls_content.append(INDENT2 "internal static extern "); \ cs_icalls_content.append(m_icall.im_type_out + " "); \ cs_icalls_content.append(m_icall.name + "("); \ cs_icalls_content.append(m_icall.im_sig + ");\n"); \ @@ -1312,7 +1316,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(itype.proxy_name); output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); - output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); output.append("\";\n"); @@ -1324,7 +1328,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } else if (is_derived_type) { // Add member fields - output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); output.append("\";\n"); @@ -1363,6 +1367,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); } + for (const List<SignalInterface>::Element *E = itype.signals_.front(); E; E = E->next()) { + const SignalInterface &isignal = E->get(); + Error method_err = _generate_cs_signal(itype, isignal, output); + ERR_FAIL_COND_V_MSG(method_err != OK, method_err, + "Failed to generate signal '" + isignal.name + "' for class '" + itype.name + "'."); + } + if (itype.is_singleton) { InternalCall singleton_icall = InternalCall(itype.api_type, ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX, "IntPtr"); @@ -1395,7 +1406,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte const TypeInterface *current_type = &p_itype; while (!setter && current_type->base_name != StringName()) { OrderedHashMap<StringName, TypeInterface>::Element base_match = obj_types.find(current_type->base_name); - ERR_FAIL_COND_V(!base_match, ERR_BUG); + ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'."); current_type = &base_match.get(); setter = current_type->find_method_by_name(p_iprop.setter); } @@ -1406,7 +1417,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte current_type = &p_itype; while (!getter && current_type->base_name != StringName()) { OrderedHashMap<StringName, TypeInterface>::Element base_match = obj_types.find(current_type->base_name); - ERR_FAIL_COND_V(!base_match, ERR_BUG); + ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'."); current_type = &base_match.get(); getter = current_type->find_method_by_name(p_iprop.getter); } @@ -1424,7 +1435,16 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte } if (getter && setter) { - ERR_FAIL_COND_V(getter->return_type.cname != setter->arguments.back()->get().type.cname, ERR_BUG); + const ArgumentInterface &setter_first_arg = setter->arguments.back()->get(); + if (getter->return_type.cname != setter_first_arg.type.cname) { + // Special case for Node::set_name + bool whitelisted = getter->return_type.cname == name_cache.type_StringName && + setter_first_arg.type.cname == name_cache.type_String; + + ERR_FAIL_COND_V_MSG(!whitelisted, ERR_BUG, + "Return type from getter doesn't match first argument of setter for property: '" + + p_itype.name + "." + String(p_iprop.cname) + "'."); + } } const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type; @@ -1474,7 +1494,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); - CRASH_COND(idx_arg_type == NULL); + CRASH_COND(idx_arg_type == nullptr); p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); } else { p_output.append(itos(p_iprop.index)); @@ -1502,7 +1522,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); - CRASH_COND(idx_arg_type == NULL); + CRASH_COND(idx_arg_type == nullptr); p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); } else { p_output.append(itos(p_iprop.index) + ", "); @@ -1525,7 +1545,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); - String method_bind_field = "method_bind_" + itos(p_method_bind_count); + String method_bind_field = "__method_bind_" + itos(p_method_bind_count); String arguments_sig; String cs_in_statements; @@ -1611,8 +1631,9 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { - p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr "); - p_output.append(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); + p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static readonly IntPtr "); + p_output.append(method_bind_field); + p_output.append(" = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); p_output.append(p_imethod.name); p_output.append("\");\n"); } @@ -1726,6 +1747,106 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf return OK; } +Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) { + String arguments_sig; + + // Retrieve information from the arguments + for (const List<ArgumentInterface>::Element *F = p_isignal.arguments.front(); F; F = F->next()) { + const ArgumentInterface &iarg = F->get(); + const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); + + // Add the current arguments to the signature + + if (F != p_isignal.arguments.front()) + arguments_sig += ", "; + + arguments_sig += arg_type->cs_type; + arguments_sig += " "; + arguments_sig += iarg.name; + } + + // Generate signal + { + if (p_isignal.method_doc && p_isignal.method_doc->description.size()) { + String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); + + if (summary_lines.size()) { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); + + for (int i = 0; i < summary_lines.size(); i++) { + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); + } + + p_output.append(INDENT2 "/// </summary>"); + } + } + + if (p_isignal.is_deprecated) { + if (p_isignal.deprecation_message.empty()) + WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); + + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(p_isignal.deprecation_message); + p_output.append("\")]"); + } + + String delegate_name = p_isignal.proxy_name; + delegate_name += "Handler"; // Delegate name is [SignalName]Handler + + // Generate delegate + p_output.append(MEMBER_BEGIN "public delegate void "); + p_output.append(delegate_name); + p_output.append("("); + p_output.append(arguments_sig); + p_output.append(");\n"); + + // TODO: + // Could we assume the StringName instance of signal name will never be freed (it's stored in ClassDB) before the managed world is unloaded? + // If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here. + + // Cached signal name (StringName) + p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static StringName __signal_name_"); + p_output.append(p_isignal.name); + p_output.append(" = \""); + p_output.append(p_isignal.name); + p_output.append("\";\n"); + + // Generate event + p_output.append(MEMBER_BEGIN "[Signal]" MEMBER_BEGIN "public "); + + if (p_itype.is_singleton) + p_output.append("static "); + + p_output.append("event "); + p_output.append(delegate_name); + p_output.append(" "); + p_output.append(p_isignal.proxy_name); + p_output.append("\n" OPEN_BLOCK_L2); + + if (p_itype.is_singleton) + p_output.append("add => Singleton.Connect(__signal_name_"); + else + p_output.append("add => Connect(__signal_name_"); + + p_output.append(p_isignal.name); + p_output.append(", new Callable(value));\n"); + + if (p_itype.is_singleton) + p_output.append(INDENT3 "remove => Singleton.Disconnect(__signal_name_"); + else + p_output.append(INDENT3 "remove => Disconnect(__signal_name_"); + + p_output.append(p_isignal.name); + p_output.append(", new Callable(value));\n"); + p_output.append(CLOSE_BLOCK_L2); + } + + return OK; +} + Error BindingsGenerator::generate_glue(const String &p_output_dir) { ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED); @@ -2000,7 +2121,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte if (return_type->is_object_type) { ptrcall_return_type = return_type->is_reference ? "Ref<Reference>" : return_type->c_type; - initialization = return_type->is_reference ? "" : " = NULL"; + initialization = return_type->is_reference ? "" : " = nullptr"; } else { ptrcall_return_type = return_type->c_type; } @@ -2009,10 +2130,10 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte p_output.append(" " C_LOCAL_RET); p_output.append(initialization + ";\n"); - String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "NULL" : return_type->c_type_out + "()"; + String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "nullptr" : return_type->c_type_out + "()"; if (return_type->ret_as_byref_arg) { - p_output.append("\tif (" CS_PARAM_INSTANCE " == NULL) { *arg_ret = "); + p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = "); p_output.append(fail_ret); p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n"); } else { @@ -2066,7 +2187,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } p_output.append(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "nullptr"); p_output.append(", total_length, vcall_error);\n"); if (!ret_void) { @@ -2077,8 +2198,8 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } } else { p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); - p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n"); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "nullptr, "); + p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "nullptr);\n"); } if (!ret_void) { @@ -2120,11 +2241,11 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con // Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead. const Map<StringName, TypeInterface>::Element *int_match = builtin_types.find(name_cache.type_int); - ERR_FAIL_NULL_V(int_match, NULL); + ERR_FAIL_NULL_V(int_match, nullptr); return &int_match->get(); } - return NULL; + return nullptr; } const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placeholder(const TypeReference &p_typeref) { @@ -2291,7 +2412,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash... - iprop.prop_doc = NULL; + iprop.prop_doc = nullptr; for (int i = 0; i < itype.class_doc->properties.size(); i++) { const DocData::PropertyDoc &prop_doc = itype.class_doc->properties[i]; @@ -2336,7 +2457,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { PropertyInfo return_info = method_info.return_val; - MethodBind *m = imethod.is_virtual ? NULL : ClassDB::get_method(type_cname, method_info.name); + MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name); imethod.is_vararg = m && m->is_vararg(); @@ -2479,13 +2600,92 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + // Populate signals + + const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; + const StringName *k = nullptr; + + while ((k = signal_map.next(k))) { + SignalInterface isignal; + + const MethodInfo &method_info = signal_map.get(*k); + + isignal.name = method_info.name; + isignal.cname = method_info.name; + + int argc = method_info.arguments.size(); + + for (int i = 0; i < argc; i++) { + PropertyInfo arginfo = method_info.arguments[i]; + + String orig_arg_name = arginfo.name; + + ArgumentInterface iarg; + iarg.name = orig_arg_name; + + if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + iarg.type.cname = arginfo.class_name; + iarg.type.is_enum = true; + } else if (arginfo.class_name != StringName()) { + iarg.type.cname = arginfo.class_name; + } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { + iarg.type.cname = arginfo.hint_string; + } else if (arginfo.type == Variant::NIL) { + iarg.type.cname = name_cache.type_Variant; + } else { + if (arginfo.type == Variant::INT) { + iarg.type.cname = _get_int_type_name_from_meta(GodotTypeInfo::METADATA_NONE); + } else if (arginfo.type == Variant::FLOAT) { + iarg.type.cname = _get_float_type_name_from_meta(GodotTypeInfo::METADATA_NONE); + } else { + iarg.type.cname = Variant::get_type_name(arginfo.type); + } + } + + iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name)); + + isignal.add_argument(iarg); + } + + isignal.proxy_name = escape_csharp_keyword(snake_to_pascal_case(isignal.name)); + + // Prevent the signal and its enclosing type from sharing the same name + if (isignal.proxy_name == itype.proxy_name) { + _log("Name of signal '%s' is ambiguous with the name of its enclosing class '%s'. Renaming signal to '%s_'\n", + isignal.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), isignal.proxy_name.utf8().get_data()); + + isignal.proxy_name += "_"; + } + + if (itype.find_property_by_proxy_name(isignal.proxy_name) || itype.find_method_by_proxy_name(isignal.proxy_name)) { + // ClassDB allows signal names that conflict with method or property names. + // While registering a signal with a conflicting name is considered wrong, + // it may still happen and it may take some time until someone fixes the name. + // We can't allow the bindings to be in a broken state while we wait for a fix; + // that's why we must handle this possibility by renaming the signal. + isignal.proxy_name += "Signal"; + } + + if (itype.class_doc) { + for (int i = 0; i < itype.class_doc->signals.size(); i++) { + const DocData::MethodDoc &signal_doc = itype.class_doc->signals[i]; + if (signal_doc.name == isignal.name) { + isignal.method_doc = &signal_doc; + break; + } + } + } + + itype.signals_.push_back(isignal); + } + // Populate enums and constants List<String> constants; ClassDB::get_integer_constant_list(type_cname, &constants, true); - const HashMap<StringName, List<StringName> > &enum_map = class_info->enum_map; - const StringName *k = NULL; + const HashMap<StringName, List<StringName>> &enum_map = class_info->enum_map; + k = nullptr; while ((k = enum_map.next(k))) { StringName enum_proxy_cname = *k; @@ -2507,7 +2707,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); - iconstant.const_doc = NULL; + iconstant.const_doc = nullptr; for (int i = 0; i < itype.class_doc->constants.size(); i++) { const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i]; @@ -2542,7 +2742,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); - iconstant.const_doc = NULL; + iconstant.const_doc = nullptr; for (int i = 0; i < itype.class_doc->constants.size(); i++) { const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i]; @@ -2587,8 +2787,15 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar #endif break; case Variant::STRING: + case Variant::STRING_NAME: case Variant::NODE_PATH: - r_iarg.default_argument = "\"" + r_iarg.default_argument + "\""; + if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) { + r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\""; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + } else { + CRASH_COND(r_iarg.type.cname != name_cache.type_String); + r_iarg.default_argument = "\"" + r_iarg.default_argument + "\""; + } break; case Variant::TRANSFORM: if (p_val.operator Transform() == Transform()) @@ -2603,8 +2810,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; break; case Variant::VECTOR2: + case Variant::VECTOR2I: case Variant::RECT2: + case Variant::RECT2I: case Variant::VECTOR3: + case Variant::VECTOR3I: r_iarg.default_argument = "new %s" + r_iarg.default_argument; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; break; @@ -2630,8 +2840,8 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar case Variant::ARRAY: case Variant::PACKED_BYTE_ARRAY: case Variant::PACKED_INT32_ARRAY: - case Variant::PACKED_FLOAT32_ARRAY: case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: case Variant::PACKED_FLOAT64_ARRAY: case Variant::PACKED_STRING_ARRAY: case Variant::PACKED_VECTOR2_ARRAY: @@ -2646,8 +2856,13 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = Variant::get_type_name(p_val.get_type()) + ".Identity"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; break; - default: { - } + case Variant::CALLABLE: + case Variant::SIGNAL: + CRASH_NOW_MSG("Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value."); + break; + default: + CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type())); + break; } if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null") @@ -2672,16 +2887,19 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = "GDMonoMarshal::M_" #m_type; \ itype.cs_in = "ref %s"; \ /* in cs_out, im_type_out (%3) includes the 'out ' part */ \ - itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; \ + itype.cs_out = "%0(%1, %3 argRet); return argRet;"; \ itype.im_type_out = "out " + itype.cs_type; \ itype.ret_as_byref_arg = true; \ builtin_types.insert(itype.cname, itype); \ } INSERT_STRUCT_TYPE(Vector2) + INSERT_STRUCT_TYPE(Vector2i) INSERT_STRUCT_TYPE(Rect2) + INSERT_STRUCT_TYPE(Rect2i) INSERT_STRUCT_TYPE(Transform2D) INSERT_STRUCT_TYPE(Vector3) + INSERT_STRUCT_TYPE(Vector3i) INSERT_STRUCT_TYPE(Basis) INSERT_STRUCT_TYPE(Quat) INSERT_STRUCT_TYPE(Transform) @@ -2749,7 +2967,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = "out " + itype.name; itype.cs_in = "ref %0"; /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.cs_out = "%0(%1, %3 argRet); return argRet;"; itype.ret_as_byref_arg = true; builtin_types.insert(itype.cname, itype); @@ -2766,7 +2984,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = "out " + itype.name; itype.cs_in = "ref %0"; /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.cs_out = "%0(%1, %3 argRet); return argRet;"; itype.ret_as_byref_arg = true; builtin_types.insert(itype.cname, itype); } @@ -2792,7 +3010,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = "out " + itype.proxy_name; itype.cs_in = "ref %0"; /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.cs_out = "%0(%1, %3 argRet); return argRet;"; itype.ret_as_byref_arg = true; builtin_types.insert(itype.cname, itype); @@ -2814,7 +3032,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = "out " + itype.proxy_name; itype.cs_in = "ref %0"; /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; + itype.cs_out = "%0(%1, %3 argRet); return argRet;"; itype.ret_as_byref_arg = true; builtin_types.insert(itype.cname, itype); } @@ -2835,6 +3053,24 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); + // StringName + itype = TypeInterface(); + itype.name = "StringName"; + itype.cname = itype.name; + itype.proxy_name = "StringName"; + itype.c_in = "\t%0 %1_in = %1 ? *%1 : StringName();\n"; + itype.c_out = "\treturn memnew(StringName(%1));\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = itype.name; + itype.c_type_in = itype.c_type + "*"; + itype.c_type_out = itype.c_type + "*"; + itype.cs_type = itype.proxy_name; + itype.cs_in = "StringName." CS_SMETHOD_GETINSTANCE "(%0)"; + itype.cs_out = "return new %2(%0(%1));"; + itype.im_type_in = "IntPtr"; + itype.im_type_out = "IntPtr"; + builtin_types.insert(itype.cname, itype); + // NodePath itype = TypeInterface(); itype.name = "NodePath"; @@ -2883,6 +3119,40 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); + // Callable + itype = TypeInterface::create_value_type(String("Callable")); + itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(*%1);\n"; + itype.c_out = "\t*%3 = " C_METHOD_MANAGED_FROM_CALLABLE "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type_in = "GDMonoMarshal::M_Callable*"; + itype.c_type_out = "GDMonoMarshal::M_Callable"; + itype.cs_in = "ref %s"; + /* in cs_out, im_type_out (%3) includes the 'out ' part */ + itype.cs_out = "%0(%1, %3 argRet); return argRet;"; + itype.im_type_out = "out " + itype.cs_type; + itype.ret_as_byref_arg = true; + builtin_types.insert(itype.cname, itype); + + // Signal + itype = TypeInterface(); + itype.name = "Signal"; + itype.cname = itype.name; + itype.proxy_name = "SignalInfo"; + itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(*%1);\n"; + itype.c_out = "\t*%3 = " C_METHOD_MANAGED_FROM_SIGNAL "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = itype.name; + itype.c_type_in = "GDMonoMarshal::M_SignalInfo*"; + itype.c_type_out = "GDMonoMarshal::M_SignalInfo"; + itype.cs_in = "ref %s"; + /* in cs_out, im_type_out (%3) includes the 'out ' part */ + itype.cs_out = "%0(%1, %3 argRet); return argRet;"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = "ref " + itype.cs_type; + itype.im_type_out = "out " + itype.cs_type; + itype.ret_as_byref_arg = true; + builtin_types.insert(itype.cname, itype); + // VarArg (fictitious type to represent variable arguments) itype = TypeInterface(); itype.name = "VarArg"; @@ -2917,13 +3187,11 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { #define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t) INSERT_ARRAY(PackedInt32Array, int); + INSERT_ARRAY(PackedInt64Array, long); INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, byte); -#ifdef REAL_T_IS_DOUBLE - INSERT_ARRAY(PackedFloat32Array, double); -#else INSERT_ARRAY(PackedFloat32Array, float); -#endif + INSERT_ARRAY(PackedFloat64Array, double); INSERT_ARRAY(PackedStringArray, string); @@ -2994,7 +3262,7 @@ void BindingsGenerator::_populate_global_constants() { String constant_name = GlobalConstants::get_global_constant_name(i); - const DocData::ConstantDoc *const_doc = NULL; + const DocData::ConstantDoc *const_doc = nullptr; for (int j = 0; j < global_scope_doc.constants.size(); j++) { const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j]; @@ -3052,7 +3320,10 @@ void BindingsGenerator::_populate_global_constants() { // HARDCODED List<StringName> hardcoded_enums; + hardcoded_enums.push_back("Vector2.Axis"); + hardcoded_enums.push_back("Vector2i.Axis"); hardcoded_enums.push_back("Vector3.Axis"); + hardcoded_enums.push_back("Vector3i.Axis"); for (List<StringName>::Element *E = hardcoded_enums.front(); E; E = E->next()) { // These enums are not generated and must be written manually (e.g.: Vector3.Axis) // Here, we assume core types do not begin with underscore diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index d3a8937313..7c87c688db 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -33,7 +33,7 @@ #include "core/class_db.h" #include "core/string_builder.h" -#include "editor/doc/doc_data.h" +#include "editor/doc_data.h" #include "editor/editor_help.h" #if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) @@ -107,9 +107,15 @@ class BindingsGenerator { TypeReference type; String name; - String default_argument; DefaultParamMode def_param_mode; + /** + * Determines the expression for the parameter default value. + * Formatting elements: + * %0 or %s: [cs_type] of the argument type + */ + String default_argument; + ArgumentInterface() { def_param_mode = CONSTANT; } @@ -170,7 +176,33 @@ class BindingsGenerator { is_virtual = false; requires_object_call = false; is_internal = false; - method_doc = NULL; + method_doc = nullptr; + is_deprecated = false; + } + }; + + struct SignalInterface { + String name; + StringName cname; + + /** + * Name of the C# method + */ + String proxy_name; + + List<ArgumentInterface> arguments; + + const DocData::MethodDoc *method_doc; + + bool is_deprecated; + String deprecation_message; + + void add_argument(const ArgumentInterface &argument) { + arguments.push_back(argument); + } + + SignalInterface() { + method_doc = nullptr; is_deprecated = false; } }; @@ -336,6 +368,7 @@ class BindingsGenerator { List<EnumInterface> enums; List<PropertyInterface> properties; List<MethodInterface> methods; + List<SignalInterface> signals_; const MethodInterface *find_method_by_name(const StringName &p_cname) const { for (const List<MethodInterface>::Element *E = methods.front(); E; E = E->next()) { @@ -343,7 +376,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } const PropertyInterface *find_property_by_name(const StringName &p_cname) const { @@ -352,7 +385,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } const PropertyInterface *find_property_by_proxy_name(const String &p_proxy_name) const { @@ -361,7 +394,16 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; + } + + const MethodInterface *find_method_by_proxy_name(const String &p_proxy_name) const { + for (const List<MethodInterface>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().proxy_name == p_proxy_name) + return &E->get(); + } + + return nullptr; } private: @@ -456,7 +498,7 @@ class BindingsGenerator { c_arg_in = "%s"; - class_doc = NULL; + class_doc = nullptr; } }; @@ -510,7 +552,7 @@ class BindingsGenerator { List<InternalCall> core_custom_icalls; List<InternalCall> editor_custom_icalls; - Map<StringName, List<StringName> > blacklisted_methods; + Map<StringName, List<StringName>> blacklisted_methods; void _initialize_blacklisted_methods(); @@ -524,6 +566,8 @@ class BindingsGenerator { StringName type_Reference; StringName type_RID; StringName type_String; + StringName type_StringName; + StringName type_NodePath; StringName type_at_GlobalScope; StringName enum_Error; @@ -548,6 +592,8 @@ class BindingsGenerator { type_Reference = StaticCString::create("Reference"); type_RID = StaticCString::create("RID"); type_String = StaticCString::create("String"); + type_StringName = StaticCString::create("StringName"); + type_NodePath = StaticCString::create("NodePath"); type_at_GlobalScope = StaticCString::create("@GlobalScope"); enum_Error = StaticCString::create("Error"); @@ -576,7 +622,7 @@ class BindingsGenerator { if (it->get().name == p_name) return it; it = it->next(); } - return NULL; + return nullptr; } const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const { @@ -585,7 +631,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } inline String get_unique_sig(const TypeInterface &p_type) { @@ -623,6 +669,7 @@ class BindingsGenerator { Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output); Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output); + Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output); void _generate_global_constants(StringBuilder &p_output); diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index 872f45ba91..e5c2d023d3 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -57,8 +57,8 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str Variant item_type = p_item_type; Variant include = p_include; const Variant *args[3] = { &project_path, &item_type, &include }; - MonoException *exc = NULL; - klass->get_method("AddItemToProjectChecked", 3)->invoke(NULL, args, &exc); + MonoException *exc = nullptr; + klass->get_method("AddItemToProjectChecked", 3)->invoke(nullptr, args, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 31996a03d0..283d4beb8e 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -95,7 +95,7 @@ MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir()); #else - return NULL; + return nullptr; #endif } @@ -103,7 +103,7 @@ MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir()); #else - return NULL; + return nullptr; #endif } @@ -111,7 +111,7 @@ MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path()); #else - return NULL; + return nullptr; #endif } @@ -119,7 +119,7 @@ MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path()); #else - return NULL; + return nullptr; #endif } @@ -127,7 +127,7 @@ MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir()); #else - return NULL; + return nullptr; #endif } @@ -135,7 +135,7 @@ MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir()); #else - return NULL; + return nullptr; #endif } @@ -151,7 +151,7 @@ MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() { #ifdef WINDOWS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir()); #else - return NULL; + return nullptr; #endif } @@ -202,7 +202,7 @@ uint32_t godot_icall_BindingsGenerator_CsGlueVersion() { } int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) { - *r_error_str = NULL; + *r_error_str = nullptr; String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); @@ -335,7 +335,7 @@ MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; return GDMonoMarshal::mono_string_from_godot(install_root_dir); #else - return NULL; + return nullptr; #endif } diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index ce0b6ad0e6..324013e5e2 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -59,7 +59,7 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> if (r_dependencies.has(ref_name)) continue; - GDMonoAssembly *ref_assembly = NULL; + GDMonoAssembly *ref_assembly = nullptr; String path; bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe"); @@ -70,21 +70,21 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> path = search_dir.plus_file(ref_name); if (FileAccess::exists(path)) { GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true); - if (ref_assembly != NULL) + if (ref_assembly != nullptr) break; } } else { path = search_dir.plus_file(ref_name + ".dll"); if (FileAccess::exists(path)) { GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); - if (ref_assembly != NULL) + if (ref_assembly != nullptr) break; } path = search_dir.plus_file(ref_name + ".exe"); if (FileAccess::exists(path)) { GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); - if (ref_assembly != NULL) + if (ref_assembly != nullptr) break; } } @@ -92,7 +92,8 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); - r_dependencies[ref_name] = ref_assembly->get_path(); + // Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir. + r_dependencies[ref_name] = path; Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); @@ -116,7 +117,7 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencie String assembly_name = *key; String assembly_path = p_initial_dependencies[*key]; - GDMonoAssembly *assembly = NULL; + GDMonoAssembly *assembly = nullptr; bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true); ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'."); diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln b/modules/mono/glue/GodotSharp/GodotSharp.sln index a496e36da3..4896d0a07d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp.sln +++ b/modules/mono/glue/GodotSharp/GodotSharp.sln @@ -8,8 +8,6 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs index 3957387be9..39d5782db8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs @@ -2,7 +2,7 @@ using System; namespace Godot { - [AttributeUsage(AttributeTargets.Delegate)] + [AttributeUsage(AttributeTargets.Delegate | AttributeTargets.Event)] public class SignalAttribute : Attribute { } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs new file mode 100644 index 0000000000..c85cc1884c --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -0,0 +1,31 @@ +using System; + +namespace Godot +{ + public struct Callable + { + private readonly Object _target; + private readonly StringName _method; + private readonly Delegate _delegate; + + public Object Target => _target; + public StringName Method => _method; + public Delegate Delegate => _delegate; + + public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate); + + public Callable(Object target, StringName method) + { + _target = target; + _method = method; + _delegate = null; + } + + public Callable(Delegate @delegate) + { + _target = null; + _method = null; + _delegate = @delegate; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs new file mode 100644 index 0000000000..785e87a043 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -0,0 +1,395 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Godot +{ + internal static class DelegateUtils + { + private enum TargetKind : uint + { + Static, + GodotObject, + CompilerGenerated + } + + internal static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData) + { + if (@delegate is MulticastDelegate multicastDelegate) + { + bool someDelegatesSerialized = false; + + Delegate[] invocationList = multicastDelegate.GetInvocationList(); + + if (invocationList.Length > 1) + { + var multiCastData = new Collections.Array(); + + foreach (Delegate oneDelegate in invocationList) + someDelegatesSerialized |= TrySerializeDelegate(oneDelegate, multiCastData); + + if (!someDelegatesSerialized) + return false; + + serializedData.Add(multiCastData); + return true; + } + } + + if (TrySerializeSingleDelegate(@delegate, out byte[] buffer)) + { + serializedData.Add(buffer); + return true; + } + + return false; + } + + private static bool TrySerializeSingleDelegate(Delegate @delegate, out byte[] buffer) + { + buffer = null; + + object target = @delegate.Target; + + switch (target) + { + case null: + { + using (var stream = new MemoryStream()) + using (var writer = new BinaryWriter(stream)) + { + writer.Write((ulong) TargetKind.Static); + + SerializeType(writer, @delegate.GetType()); + + if (!TrySerializeMethodInfo(writer, @delegate.Method)) + return false; + + buffer = stream.ToArray(); + return true; + } + } + case Godot.Object godotObject: + { + using (var stream = new MemoryStream()) + using (var writer = new BinaryWriter(stream)) + { + writer.Write((ulong) TargetKind.GodotObject); + writer.Write((ulong) godotObject.GetInstanceId()); + + SerializeType(writer, @delegate.GetType()); + + if (!TrySerializeMethodInfo(writer, @delegate.Method)) + return false; + + buffer = stream.ToArray(); + return true; + } + } + default: + { + Type targetType = target.GetType(); + + if (targetType.GetCustomAttribute(typeof(CompilerGeneratedAttribute), true) != null) + { + // Compiler generated. Probably a closure. Try to serialize it. + + using (var stream = new MemoryStream()) + using (var writer = new BinaryWriter(stream)) + { + writer.Write((ulong) TargetKind.CompilerGenerated); + SerializeType(writer, targetType); + + SerializeType(writer, @delegate.GetType()); + + if (!TrySerializeMethodInfo(writer, @delegate.Method)) + return false; + + FieldInfo[] fields = targetType.GetFields(BindingFlags.Instance | BindingFlags.Public); + + writer.Write(fields.Length); + + foreach (FieldInfo field in fields) + { + Type fieldType = field.GetType(); + + Variant.Type variantType = GD.TypeToVariantType(fieldType); + + if (variantType == Variant.Type.Nil) + return false; + + writer.Write(field.Name); + byte[] valueBuffer = GD.Var2Bytes(field.GetValue(target)); + writer.Write(valueBuffer.Length); + writer.Write(valueBuffer); + } + + buffer = stream.ToArray(); + return true; + } + } + + return false; + } + } + } + + private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo) + { + if (methodInfo == null) + return false; + + SerializeType(writer, methodInfo.DeclaringType); + + writer.Write(methodInfo.Name); + + int flags = 0; + + if (methodInfo.IsPublic) + flags |= (int) BindingFlags.Public; + else + flags |= (int) BindingFlags.NonPublic; + + if (methodInfo.IsStatic) + flags |= (int) BindingFlags.Static; + else + flags |= (int) BindingFlags.Instance; + + writer.Write(flags); + + Type returnType = methodInfo.ReturnType; + bool hasReturn = methodInfo.ReturnType != typeof(void); + + writer.Write(hasReturn); + if (hasReturn) + SerializeType(writer, returnType); + + ParameterInfo[] parameters = methodInfo.GetParameters(); + + writer.Write(parameters.Length); + + if (parameters.Length > 0) + { + for (int i = 0; i < parameters.Length; i++) + SerializeType(writer, parameters[i].ParameterType); + } + + return true; + } + + private static void SerializeType(BinaryWriter writer, Type type) + { + if (type == null) + { + int genericArgumentsCount = -1; + writer.Write(genericArgumentsCount); + } + else if (type.IsGenericType) + { + Type genericTypeDef = type.GetGenericTypeDefinition(); + Type[] genericArgs = type.GetGenericArguments(); + + int genericArgumentsCount = genericArgs.Length; + writer.Write(genericArgumentsCount); + + string assemblyQualifiedName = genericTypeDef.AssemblyQualifiedName; + Debug.Assert(assemblyQualifiedName != null); + writer.Write(assemblyQualifiedName); + + for (int i = 0; i < genericArgs.Length; i++) + SerializeType(writer, genericArgs[i]); + } + else + { + int genericArgumentsCount = 0; + writer.Write(genericArgumentsCount); + + string assemblyQualifiedName = type.AssemblyQualifiedName; + Debug.Assert(assemblyQualifiedName != null); + writer.Write(assemblyQualifiedName); + } + } + + private static bool TryDeserializeDelegate(Collections.Array serializedData, out Delegate @delegate) + { + if (serializedData.Count == 1) + { + object elem = serializedData[0]; + + if (elem is Collections.Array multiCastData) + return TryDeserializeDelegate(multiCastData, out @delegate); + + return TryDeserializeSingleDelegate((byte[])elem, out @delegate); + } + + @delegate = null; + + var delegates = new List<Delegate>(serializedData.Count); + + foreach (object elem in serializedData) + { + if (elem is Collections.Array multiCastData) + { + if (TryDeserializeDelegate(multiCastData, out Delegate oneDelegate)) + delegates.Add(oneDelegate); + } + else + { + if (TryDeserializeSingleDelegate((byte[]) elem, out Delegate oneDelegate)) + delegates.Add(oneDelegate); + } + } + + if (delegates.Count <= 0) + return false; + + @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray()); + return true; + } + + private static bool TryDeserializeSingleDelegate(byte[] buffer, out Delegate @delegate) + { + @delegate = null; + + using (var stream = new MemoryStream(buffer, writable: false)) + using (var reader = new BinaryReader(stream)) + { + var targetKind = (TargetKind) reader.ReadUInt64(); + + switch (targetKind) + { + case TargetKind.Static: + { + Type delegateType = DeserializeType(reader); + if (delegateType == null) + return false; + + if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + return false; + + @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo); + return true; + } + case TargetKind.GodotObject: + { + ulong objectId = reader.ReadUInt64(); + Godot.Object godotObject = GD.InstanceFromId(objectId); + if (godotObject == null) + return false; + + Type delegateType = DeserializeType(reader); + if (delegateType == null) + return false; + + if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + return false; + + @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo); + return true; + } + case TargetKind.CompilerGenerated: + { + Type targetType = DeserializeType(reader); + if (targetType == null) + return false; + + Type delegateType = DeserializeType(reader); + if (delegateType == null) + return false; + + if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + return false; + + int fieldCount = reader.ReadInt32(); + + object recreatedTarget = Activator.CreateInstance(targetType); + + for (int i = 0; i < fieldCount; i++) + { + string name = reader.ReadString(); + int valueBufferLength = reader.ReadInt32(); + byte[] valueBuffer = reader.ReadBytes(valueBufferLength); + + FieldInfo fieldInfo = targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public); + fieldInfo?.SetValue(recreatedTarget, GD.Bytes2Var(valueBuffer)); + } + + @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo); + return true; + } + default: + return false; + } + } + } + + private static bool TryDeserializeMethodInfo(BinaryReader reader, out MethodInfo methodInfo) + { + methodInfo = null; + + Type declaringType = DeserializeType(reader); + + string methodName = reader.ReadString(); + + int flags = reader.ReadInt32(); + + bool hasReturn = reader.ReadBoolean(); + Type returnType = hasReturn ? DeserializeType(reader) : typeof(void); + + int parametersCount = reader.ReadInt32(); + + if (parametersCount > 0) + { + var parameterTypes = new Type[parametersCount]; + + for (int i = 0; i < parametersCount; i++) + { + Type parameterType = DeserializeType(reader); + if (parameterType == null) + return false; + parameterTypes[i] = parameterType; + } + + methodInfo = declaringType.GetMethod(methodName, (BindingFlags) flags, null, parameterTypes, null); + return methodInfo != null && methodInfo.ReturnType == returnType; + } + + methodInfo = declaringType.GetMethod(methodName, (BindingFlags) flags); + return methodInfo != null && methodInfo.ReturnType == returnType; + } + + private static Type DeserializeType(BinaryReader reader) + { + int genericArgumentsCount = reader.ReadInt32(); + + if (genericArgumentsCount == -1) + return null; + + string assemblyQualifiedName = reader.ReadString(); + var type = Type.GetType(assemblyQualifiedName); + + if (type == null) + return null; // Type not found + + if (genericArgumentsCount != 0) + { + var genericArgumentTypes = new Type[genericArgumentsCount]; + + for (int i = 0; i < genericArgumentsCount; i++) + { + Type genericArgumentType = DeserializeType(reader); + if (genericArgumentType == null) + return null; + genericArgumentTypes[i] = genericArgumentType; + } + + type = type.MakeGenericType(genericArgumentTypes); + } + + return type; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs index a0f105d55e..c4c911b863 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs @@ -23,7 +23,7 @@ namespace Godot /// <example> /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Godot.Object"/>. /// <code> - /// dynamic sprite = GetNode("Sprite").DynamicGodotObject; + /// dynamic sprite = GetNode("Sprite2D").DynamicGodotObject; /// sprite.add_child(this); /// /// if ((sprite.hframes * sprite.vframes) > 0) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs index 684d160b57..5f64c09a89 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs @@ -2,9 +2,9 @@ namespace Godot { public static partial class ResourceLoader { - public static T Load<T>(string path) where T : class + public static T Load<T>(string path, string typeHint = null, bool noCache = false) where T : class { - return (T)(object)Load(path); + return (T)(object)Load(path, typeHint, noCache); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 2a9c2d73b1..9384da0e48 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using real_t = System.Double; #else using real_t = System.Single; + #endif // TODO: Add comments describing what this class does. It is not obvious. @@ -13,9 +14,9 @@ namespace Godot { public static partial class GD { - public static object Bytes2Var(byte[] bytes, bool allow_objects = false) + public static object Bytes2Var(byte[] bytes, bool allowObjects = false) { - return godot_icall_GD_bytes2var(bytes, allow_objects); + return godot_icall_GD_bytes2var(bytes, allowObjects); } public static object Convert(object what, Variant.Type type) @@ -25,7 +26,7 @@ namespace Godot public static real_t Db2Linear(real_t db) { - return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); + return (real_t) Math.Exp(db * 0.11512925464970228420089957273422); } public static real_t DecTime(real_t value, real_t amount, real_t step) @@ -38,11 +39,11 @@ namespace Godot return val * sgn; } - public static FuncRef FuncRef(Object instance, string funcname) + public static FuncRef FuncRef(Object instance, StringName funcName) { var ret = new FuncRef(); ret.SetInstance(instance); - ret.SetFunction(funcname); + ret.SetFunction(funcName); return ret; } @@ -58,7 +59,7 @@ namespace Godot public static real_t Linear2Db(real_t linear) { - return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321); + return (real_t) (Math.Log(linear) * 8.6858896380650365530225783783321); } public static Resource Load(string path) @@ -181,14 +182,14 @@ namespace Godot return godot_icall_GD_str2var(str); } - public static bool TypeExists(string type) + public static bool TypeExists(StringName type) { - return godot_icall_GD_type_exists(type); + return godot_icall_GD_type_exists(StringName.GetPtr(type)); } - public static byte[] Var2Bytes(object var, bool full_objects = false) + public static byte[] Var2Bytes(object var, bool fullObjects = false) { - return godot_icall_GD_var2bytes(var, full_objects); + return godot_icall_GD_var2bytes(var, fullObjects); } public static string Var2Str(object var) @@ -196,8 +197,13 @@ namespace Godot return godot_icall_GD_var2str(var); } + public static Variant.Type TypeToVariantType(Type type) + { + return godot_icall_TypeToVariantType(type); + } + [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_GD_bytes2var(byte[] bytes, bool allow_objects); + internal extern static object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects); [MethodImpl(MethodImplOptions.InternalCall)] internal extern static object godot_icall_GD_convert(object what, Variant.Type type); @@ -206,7 +212,7 @@ namespace Godot internal extern static int godot_icall_GD_hash(object var); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Object godot_icall_GD_instance_from_id(ulong instance_id); + internal extern static Object godot_icall_GD_instance_from_id(ulong instanceId); [MethodImpl(MethodImplOptions.InternalCall)] internal extern static void godot_icall_GD_print(object[] what); @@ -249,10 +255,10 @@ namespace Godot internal extern static object godot_icall_GD_str2var(string str); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_GD_type_exists(string type); + internal extern static bool godot_icall_GD_type_exists(IntPtr type); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static byte[] godot_icall_GD_var2bytes(object what, bool full_objects); + internal extern static byte[] godot_icall_GD_var2bytes(object what, bool fullObjects); [MethodImpl(MethodImplOptions.InternalCall)] internal extern static string godot_icall_GD_var2str(object var); @@ -262,5 +268,8 @@ namespace Godot [MethodImpl(MethodImplOptions.InternalCall)] internal extern static void godot_icall_GD_pushwarning(string type); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Variant.Type godot_icall_TypeToVariantType(Type type); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs index 8c5872ba5a..4ecc55f94e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs @@ -7,7 +7,7 @@ namespace Godot { private bool disposed = false; - internal IntPtr ptr; + private IntPtr ptr; internal static IntPtr GetPtr(NodePath instance) { @@ -50,104 +50,93 @@ namespace Godot this.ptr = ptr; } - public IntPtr NativeInstance - { - get { return ptr; } - } - public NodePath() : this(string.Empty) {} public NodePath(string path) { - this.ptr = godot_icall_NodePath_Ctor(path); + ptr = godot_icall_NodePath_Ctor(path); } - public static implicit operator NodePath(string from) - { - return new NodePath(from); - } + public static implicit operator NodePath(string from) => new NodePath(from); - public static implicit operator string(NodePath from) - { - return godot_icall_NodePath_operator_String(NodePath.GetPtr(from)); - } + public static implicit operator string(NodePath from) => from.ToString(); public override string ToString() { - return (string)this; + return godot_icall_NodePath_operator_String(GetPtr(this)); } public NodePath GetAsPropertyPath() { - return new NodePath(godot_icall_NodePath_get_as_property_path(NodePath.GetPtr(this))); + return new NodePath(godot_icall_NodePath_get_as_property_path(GetPtr(this))); } public string GetConcatenatedSubnames() { - return godot_icall_NodePath_get_concatenated_subnames(NodePath.GetPtr(this)); + return godot_icall_NodePath_get_concatenated_subnames(GetPtr(this)); } public string GetName(int idx) { - return godot_icall_NodePath_get_name(NodePath.GetPtr(this), idx); + return godot_icall_NodePath_get_name(GetPtr(this), idx); } public int GetNameCount() { - return godot_icall_NodePath_get_name_count(NodePath.GetPtr(this)); + return godot_icall_NodePath_get_name_count(GetPtr(this)); } public string GetSubname(int idx) { - return godot_icall_NodePath_get_subname(NodePath.GetPtr(this), idx); + return godot_icall_NodePath_get_subname(GetPtr(this), idx); } public int GetSubnameCount() { - return godot_icall_NodePath_get_subname_count(NodePath.GetPtr(this)); + return godot_icall_NodePath_get_subname_count(GetPtr(this)); } public bool IsAbsolute() { - return godot_icall_NodePath_is_absolute(NodePath.GetPtr(this)); + return godot_icall_NodePath_is_absolute(GetPtr(this)); } public bool IsEmpty() { - return godot_icall_NodePath_is_empty(NodePath.GetPtr(this)); + return godot_icall_NodePath_is_empty(GetPtr(this)); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_NodePath_Ctor(string path); + private static extern IntPtr godot_icall_NodePath_Ctor(string path); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_NodePath_Dtor(IntPtr ptr); + private static extern void godot_icall_NodePath_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_NodePath_operator_String(IntPtr ptr); + private static extern string godot_icall_NodePath_operator_String(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr); + private static extern IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr); + private static extern string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_NodePath_get_name(IntPtr ptr, int arg1); + private static extern string godot_icall_NodePath_get_name(IntPtr ptr, int arg1); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_NodePath_get_name_count(IntPtr ptr); + private static extern int godot_icall_NodePath_get_name_count(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_NodePath_get_subname(IntPtr ptr, int arg1); + private static extern string godot_icall_NodePath_get_subname(IntPtr ptr, int arg1); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_NodePath_get_subname_count(IntPtr ptr); + private static extern int godot_icall_NodePath_get_subname_count(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_NodePath_is_absolute(IntPtr ptr); + private static extern bool godot_icall_NodePath_is_absolute(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_NodePath_is_empty(IntPtr ptr); + private static extern bool godot_icall_NodePath_is_empty(IntPtr ptr); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index de80f7fddc..42610c5ef7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -7,7 +7,7 @@ namespace Godot { private bool disposed = false; - private const string nativeName = "Object"; + private static StringName nativeName = "Object"; internal IntPtr ptr; internal bool memoryOwn; @@ -15,7 +15,14 @@ namespace Godot public Object() : this(false) { if (ptr == IntPtr.Zero) + { ptr = godot_icall_Object_Ctor(this); + } + else + { + // This is called inside godot_icall_Object_Ctor, so we must call it as well in this case. + godot_icall_Object_ConnectEventSignals(ptr); + } } internal Object(bool memoryOwn) @@ -101,7 +108,7 @@ namespace Godot /// } /// </code> /// </example> - public SignalAwaiter ToSignal(Object source, string signal) + public SignalAwaiter ToSignal(Object source, StringName signal) { return new SignalAwaiter(source, signal, this); } @@ -111,20 +118,28 @@ namespace Godot /// </summary> public dynamic DynamicObject => new DynamicGodotObject(this); + internal static IntPtr __ClassDB_get_method(StringName type, string method) + { + return godot_icall_Object_ClassDB_get_method(StringName.GetPtr(type), method); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern IntPtr godot_icall_Object_Ctor(Object obj); + [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Object_Ctor(Object obj); + internal static extern void godot_icall_Object_Disposed(Object obj, IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Object_Disposed(Object obj, IntPtr ptr); + internal static extern void godot_icall_Reference_Disposed(Object obj, IntPtr ptr, bool isFinalizer); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Reference_Disposed(Object obj, IntPtr ptr, bool isFinalizer); + internal static extern void godot_icall_Object_ConnectEventSignals(IntPtr obj); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_Object_ToString(IntPtr ptr); + internal static extern string godot_icall_Object_ToString(IntPtr ptr); // Used by the generated API [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Object_ClassDB_get_method(string type, string method); + internal static extern IntPtr godot_icall_Object_ClassDB_get_method(IntPtr type, string method); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs new file mode 100644 index 0000000000..bc2cad8713 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs @@ -0,0 +1,262 @@ +using System; +using System.Runtime.InteropServices; + +namespace Godot +{ + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct Rect2i : IEquatable<Rect2i> + { + private Vector2i _position; + private Vector2i _size; + + public Vector2i Position + { + get { return _position; } + set { _position = value; } + } + + public Vector2i Size + { + get { return _size; } + set { _size = value; } + } + + public Vector2i End + { + get { return _position + _size; } + set { _size = value - _position; } + } + + public int Area + { + get { return GetArea(); } + } + + public Rect2i Abs() + { + Vector2i end = End; + Vector2i topLeft = new Vector2i(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y)); + return new Rect2i(topLeft, _size.Abs()); + } + + public Rect2i Clip(Rect2i b) + { + var newRect = b; + + if (!Intersects(newRect)) + return new Rect2i(); + + newRect._position.x = Mathf.Max(b._position.x, _position.x); + newRect._position.y = Mathf.Max(b._position.y, _position.y); + + Vector2i bEnd = b._position + b._size; + Vector2i end = _position + _size; + + newRect._size.x = Mathf.Min(bEnd.x, end.x) - newRect._position.x; + newRect._size.y = Mathf.Min(bEnd.y, end.y) - newRect._position.y; + + return newRect; + } + + public bool Encloses(Rect2i b) + { + return b._position.x >= _position.x && b._position.y >= _position.y && + b._position.x + b._size.x < _position.x + _size.x && + b._position.y + b._size.y < _position.y + _size.y; + } + + public Rect2i Expand(Vector2i to) + { + var expanded = this; + + Vector2i begin = expanded._position; + Vector2i end = expanded._position + expanded._size; + + if (to.x < begin.x) + begin.x = to.x; + if (to.y < begin.y) + begin.y = to.y; + + if (to.x > end.x) + end.x = to.x; + if (to.y > end.y) + end.y = to.y; + + expanded._position = begin; + expanded._size = end - begin; + + return expanded; + } + + public int GetArea() + { + return _size.x * _size.y; + } + + public Rect2i Grow(int by) + { + var g = this; + + g._position.x -= by; + g._position.y -= by; + g._size.x += by * 2; + g._size.y += by * 2; + + return g; + } + + public Rect2i GrowIndividual(int left, int top, int right, int bottom) + { + var g = this; + + g._position.x -= left; + g._position.y -= top; + g._size.x += left + right; + g._size.y += top + bottom; + + return g; + } + + public Rect2i GrowMargin(Margin margin, int by) + { + var g = this; + + g.GrowIndividual(Margin.Left == margin ? by : 0, + Margin.Top == margin ? by : 0, + Margin.Right == margin ? by : 0, + Margin.Bottom == margin ? by : 0); + + return g; + } + + public bool HasNoArea() + { + return _size.x <= 0 || _size.y <= 0; + } + + public bool HasPoint(Vector2i point) + { + if (point.x < _position.x) + return false; + if (point.y < _position.y) + return false; + + if (point.x >= _position.x + _size.x) + return false; + if (point.y >= _position.y + _size.y) + return false; + + return true; + } + + public bool Intersects(Rect2i b) + { + if (_position.x >= b._position.x + b._size.x) + return false; + if (_position.x + _size.x <= b._position.x) + return false; + if (_position.y >= b._position.y + b._size.y) + return false; + if (_position.y + _size.y <= b._position.y) + return false; + + return true; + } + + public Rect2i Merge(Rect2i b) + { + Rect2i newRect; + + newRect._position.x = Mathf.Min(b._position.x, _position.x); + newRect._position.y = Mathf.Min(b._position.y, _position.y); + + newRect._size.x = Mathf.Max(b._position.x + b._size.x, _position.x + _size.x); + newRect._size.y = Mathf.Max(b._position.y + b._size.y, _position.y + _size.y); + + newRect._size = newRect._size - newRect._position; // Make relative again + + return newRect; + } + + // Constructors + public Rect2i(Vector2i position, Vector2i size) + { + _position = position; + _size = size; + } + public Rect2i(Vector2i position, int width, int height) + { + _position = position; + _size = new Vector2i(width, height); + } + public Rect2i(int x, int y, Vector2i size) + { + _position = new Vector2i(x, y); + _size = size; + } + public Rect2i(int x, int y, int width, int height) + { + _position = new Vector2i(x, y); + _size = new Vector2i(width, height); + } + + public static bool operator ==(Rect2i left, Rect2i right) + { + return left.Equals(right); + } + + public static bool operator !=(Rect2i left, Rect2i right) + { + return !left.Equals(right); + } + + public static implicit operator Rect2(Rect2i value) + { + return new Rect2(value._position, value._size); + } + + public static explicit operator Rect2i(Rect2 value) + { + return new Rect2i((Vector2i)value.Position, (Vector2i)value.Size); + } + + public override bool Equals(object obj) + { + if (obj is Rect2i) + { + return Equals((Rect2i)obj); + } + + return false; + } + + public bool Equals(Rect2i other) + { + return _position.Equals(other._position) && _size.Equals(other._size); + } + + public override int GetHashCode() + { + return _position.GetHashCode() ^ _size.GetHashCode(); + } + + public override string ToString() + { + return String.Format("{0}, {1}", new object[] + { + _position.ToString(), + _size.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("{0}, {1}", new object[] + { + _position.ToString(format), + _size.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs index 9483b6ffb4..4dc630238b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs @@ -9,13 +9,13 @@ namespace Godot private object[] result; private Action action; - public SignalAwaiter(Object source, string signal, Object target) + public SignalAwaiter(Object source, StringName signal, Object target) { - godot_icall_SignalAwaiter_connect(Object.GetPtr(source), signal, Object.GetPtr(target), this); + godot_icall_SignalAwaiter_connect(Object.GetPtr(source), StringName.GetPtr(signal), Object.GetPtr(target), this); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Error godot_icall_SignalAwaiter_connect(IntPtr source, string signal, IntPtr target, SignalAwaiter awaiter); + internal extern static Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter); public bool IsCompleted { @@ -50,11 +50,5 @@ namespace Godot action(); } } - - internal void FailureCallback() - { - action = null; - completed = true; - } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs new file mode 100644 index 0000000000..dc92de7a61 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs @@ -0,0 +1,17 @@ +namespace Godot +{ + public struct SignalInfo + { + private readonly Object _owner; + private readonly StringName _signalName; + + public Object Owner => _owner; + public StringName Name => _signalName; + + public SignalInfo(Object owner, StringName name) + { + _owner = owner; + _signalName = name; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs new file mode 100644 index 0000000000..7700b6d4ed --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs @@ -0,0 +1,82 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Godot +{ + public sealed partial class StringName : IDisposable + { + private IntPtr ptr; + + internal static IntPtr GetPtr(StringName instance) + { + if (instance == null) + throw new NullReferenceException($"The instance of type {nameof(StringName)} is null."); + + if (instance.ptr == IntPtr.Zero) + throw new ObjectDisposedException(instance.GetType().FullName); + + return instance.ptr; + } + + ~StringName() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (ptr != IntPtr.Zero) + { + godot_icall_StringName_Dtor(ptr); + ptr = IntPtr.Zero; + } + } + + internal StringName(IntPtr ptr) + { + this.ptr = ptr; + } + + public StringName() + { + ptr = IntPtr.Zero; + } + + public StringName(string path) + { + ptr = path == null ? IntPtr.Zero : godot_icall_StringName_Ctor(path); + } + + public static implicit operator StringName(string from) => new StringName(from); + + public static implicit operator string(StringName from) => from.ToString(); + + public override string ToString() + { + return ptr == IntPtr.Zero ? string.Empty : godot_icall_StringName_operator_String(GetPtr(this)); + } + + public bool IsEmpty() + { + return ptr == IntPtr.Zero || godot_icall_StringName_is_empty(GetPtr(this)); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr godot_icall_StringName_Ctor(string path); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void godot_icall_StringName_Dtor(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string godot_icall_StringName_operator_String(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool godot_icall_StringName_is_empty(IntPtr ptr); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 385bfed122..f7b13198f8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -76,11 +76,6 @@ namespace Godot } } - public real_t Cross(Vector2 b) - { - return x * b.y - y * b.x; - } - public Vector2 Abs() { return new Vector2(Mathf.Abs(x), Mathf.Abs(y)); @@ -130,6 +125,11 @@ namespace Godot return v; } + public real_t Cross(Vector2 b) + { + return x * b.y - y * b.x; + } + public Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t t) { var p0 = preA; @@ -234,7 +234,7 @@ namespace Godot public Vector2 Reflect(Vector2 n) { - return 2.0f * n * Dot(n) - this; + return 2 * Dot(n) * n - this; } public Vector2 Rotated(real_t phi) @@ -352,18 +352,18 @@ namespace Godot return left; } - public static Vector2 operator /(Vector2 vec, real_t scale) + public static Vector2 operator /(Vector2 vec, real_t divisor) { - vec.x /= scale; - vec.y /= scale; + vec.x /= divisor; + vec.y /= divisor; return vec; } - public static Vector2 operator /(Vector2 left, Vector2 right) + public static Vector2 operator /(Vector2 vec, Vector2 divisorv) { - left.x /= right.x; - left.y /= right.y; - return left; + vec.x /= divisorv.x; + vec.y /= divisorv.y; + return vec; } public static Vector2 operator %(Vector2 vec, real_t divisor) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs new file mode 100644 index 0000000000..7dc22d7918 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -0,0 +1,380 @@ +using System; +using System.Runtime.InteropServices; + +#if REAL_T_IS_DOUBLE +using real_t = System.Double; +#else +using real_t = System.Single; +#endif + +namespace Godot +{ + /// <summary> + /// 2-element structure that can be used to represent 2D grid coordinates or pairs of integers. + /// </summary> + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct Vector2i : IEquatable<Vector2i> + { + public enum Axis + { + X = 0, + Y + } + + public int x; + public int y; + + public int this[int index] + { + get + { + switch (index) + { + case 0: + return x; + case 1: + return y; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + public Vector2i Abs() + { + return new Vector2i(Mathf.Abs(x), Mathf.Abs(y)); + } + + public real_t Angle() + { + return Mathf.Atan2(y, x); + } + + public real_t AngleTo(Vector2i to) + { + return Mathf.Atan2(Cross(to), Dot(to)); + } + + public real_t AngleToPoint(Vector2i to) + { + return Mathf.Atan2(y - to.y, x - to.x); + } + + public real_t Aspect() + { + return x / (real_t)y; + } + + public Vector2i Bounce(Vector2i n) + { + return -Reflect(n); + } + + public int Cross(Vector2i b) + { + return x * b.y - y * b.x; + } + + public int DistanceSquaredTo(Vector2i b) + { + return (b - this).LengthSquared(); + } + + public real_t DistanceTo(Vector2i b) + { + return (b - this).Length(); + } + + public int Dot(Vector2i b) + { + return x * b.x + y * b.y; + } + + public real_t Length() + { + int x2 = x * x; + int y2 = y * y; + + return Mathf.Sqrt(x2 + y2); + } + + public int LengthSquared() + { + int x2 = x * x; + int y2 = y * y; + + return x2 + y2; + } + + public Axis MaxAxis() + { + return x < y ? Axis.Y : Axis.X; + } + + public Axis MinAxis() + { + return x > y ? Axis.Y : Axis.X; + } + + public Vector2i PosMod(int mod) + { + Vector2i v = this; + v.x = Mathf.PosMod(v.x, mod); + v.y = Mathf.PosMod(v.y, mod); + return v; + } + + public Vector2i PosMod(Vector2i modv) + { + Vector2i v = this; + v.x = Mathf.PosMod(v.x, modv.x); + v.y = Mathf.PosMod(v.y, modv.y); + return v; + } + + public Vector2i Reflect(Vector2i n) + { + return 2 * Dot(n) * n - this; + } + + public Vector2i Sign() + { + Vector2i v = this; + v.x = Mathf.Sign(v.x); + v.y = Mathf.Sign(v.y); + return v; + } + + public Vector2i Tangent() + { + return new Vector2i(y, -x); + } + + // Constants + private static readonly Vector2i _zero = new Vector2i(0, 0); + private static readonly Vector2i _one = new Vector2i(1, 1); + + private static readonly Vector2i _up = new Vector2i(0, -1); + private static readonly Vector2i _down = new Vector2i(0, 1); + private static readonly Vector2i _right = new Vector2i(1, 0); + private static readonly Vector2i _left = new Vector2i(-1, 0); + + public static Vector2i Zero { get { return _zero; } } + public static Vector2i One { get { return _one; } } + + public static Vector2i Up { get { return _up; } } + public static Vector2i Down { get { return _down; } } + public static Vector2i Right { get { return _right; } } + public static Vector2i Left { get { return _left; } } + + // Constructors + public Vector2i(int x, int y) + { + this.x = x; + this.y = y; + } + public Vector2i(Vector2i vi) + { + this.x = vi.x; + this.y = vi.y; + } + public Vector2i(Vector2 v) + { + this.x = Mathf.RoundToInt(v.x); + this.y = Mathf.RoundToInt(v.y); + } + + public static Vector2i operator +(Vector2i left, Vector2i right) + { + left.x += right.x; + left.y += right.y; + return left; + } + + public static Vector2i operator -(Vector2i left, Vector2i right) + { + left.x -= right.x; + left.y -= right.y; + return left; + } + + public static Vector2i operator -(Vector2i vec) + { + vec.x = -vec.x; + vec.y = -vec.y; + return vec; + } + + public static Vector2i operator *(Vector2i vec, int scale) + { + vec.x *= scale; + vec.y *= scale; + return vec; + } + + public static Vector2i operator *(int scale, Vector2i vec) + { + vec.x *= scale; + vec.y *= scale; + return vec; + } + + public static Vector2i operator *(Vector2i left, Vector2i right) + { + left.x *= right.x; + left.y *= right.y; + return left; + } + + public static Vector2i operator /(Vector2i vec, int divisor) + { + vec.x /= divisor; + vec.y /= divisor; + return vec; + } + + public static Vector2i operator /(Vector2i vec, Vector2i divisorv) + { + vec.x /= divisorv.x; + vec.y /= divisorv.y; + return vec; + } + + public static Vector2i operator %(Vector2i vec, int divisor) + { + vec.x %= divisor; + vec.y %= divisor; + return vec; + } + + public static Vector2i operator %(Vector2i vec, Vector2i divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + return vec; + } + + public static Vector2i operator &(Vector2i vec, int and) + { + vec.x &= and; + vec.y &= and; + return vec; + } + + public static Vector2i operator &(Vector2i vec, Vector2i andv) + { + vec.x &= andv.x; + vec.y &= andv.y; + return vec; + } + + public static bool operator ==(Vector2i left, Vector2i right) + { + return left.Equals(right); + } + + public static bool operator !=(Vector2i left, Vector2i right) + { + return !left.Equals(right); + } + + public static bool operator <(Vector2i left, Vector2i right) + { + if (left.x.Equals(right.x)) + { + return left.y < right.y; + } + return left.x < right.x; + } + + public static bool operator >(Vector2i left, Vector2i right) + { + if (left.x.Equals(right.x)) + { + return left.y > right.y; + } + return left.x > right.x; + } + + public static bool operator <=(Vector2i left, Vector2i right) + { + if (left.x.Equals(right.x)) + { + return left.y <= right.y; + } + return left.x <= right.x; + } + + public static bool operator >=(Vector2i left, Vector2i right) + { + if (left.x.Equals(right.x)) + { + return left.y >= right.y; + } + return left.x >= right.x; + } + + public static implicit operator Vector2(Vector2i value) + { + return new Vector2(value.x, value.y); + } + + public static explicit operator Vector2i(Vector2 value) + { + return new Vector2i(value); + } + + public override bool Equals(object obj) + { + if (obj is Vector2i) + { + return Equals((Vector2i)obj); + } + + return false; + } + + public bool Equals(Vector2i other) + { + return x == other.x && y == other.y; + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ x.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1})", new object[] + { + this.x.ToString(), + this.y.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1})", new object[] + { + this.x.ToString(format), + this.y.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 390036c654..a43836e985 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -400,20 +400,20 @@ namespace Godot return left; } - public static Vector3 operator /(Vector3 vec, real_t scale) + public static Vector3 operator /(Vector3 vec, real_t divisor) { - vec.x /= scale; - vec.y /= scale; - vec.z /= scale; + vec.x /= divisor; + vec.y /= divisor; + vec.z /= divisor; return vec; } - public static Vector3 operator /(Vector3 left, Vector3 right) + public static Vector3 operator /(Vector3 vec, Vector3 divisorv) { - left.x /= right.x; - left.y /= right.y; - left.z /= right.z; - return left; + vec.x /= divisorv.x; + vec.y /= divisorv.y; + vec.z /= divisorv.z; + return vec; } public static Vector3 operator %(Vector3 vec, real_t divisor) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs new file mode 100644 index 0000000000..c17f900131 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -0,0 +1,402 @@ +using System; +using System.Runtime.InteropServices; + +#if REAL_T_IS_DOUBLE +using real_t = System.Double; +#else +using real_t = System.Single; +#endif + +namespace Godot +{ + /// <summary> + /// 3-element structure that can be used to represent 3D grid coordinates or sets of integers. + /// </summary> + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct Vector3i : IEquatable<Vector3i> + { + public enum Axis + { + X = 0, + Y, + Z + } + + public int x; + public int y; + public int z; + + public int this[int index] + { + get + { + switch (index) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + public Vector3i Abs() + { + Vector3i v = this; + if (v.x < 0) + { + v.x = -v.x; + } + if (v.y < 0) + { + v.y = -v.y; + } + if (v.z < 0) + { + v.z = -v.z; + } + return v; + } + + public int DistanceSquaredTo(Vector3i b) + { + return (b - this).LengthSquared(); + } + + public real_t DistanceTo(Vector3i b) + { + return (b - this).Length(); + } + + public int Dot(Vector3i b) + { + return x * b.x + y * b.y + z * b.z; + } + + public real_t Length() + { + int x2 = x * x; + int y2 = y * y; + int z2 = z * z; + + return Mathf.Sqrt(x2 + y2 + z2); + } + + public int LengthSquared() + { + int x2 = x * x; + int y2 = y * y; + int z2 = z * z; + + return x2 + y2 + z2; + } + + public Axis MaxAxis() + { + return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X); + } + + public Axis MinAxis() + { + return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z); + } + + public Vector3i PosMod(int mod) + { + Vector3i v = this; + v.x = Mathf.PosMod(v.x, mod); + v.y = Mathf.PosMod(v.y, mod); + v.z = Mathf.PosMod(v.z, mod); + return v; + } + + public Vector3i PosMod(Vector3i modv) + { + Vector3i v = this; + v.x = Mathf.PosMod(v.x, modv.x); + v.y = Mathf.PosMod(v.y, modv.y); + v.z = Mathf.PosMod(v.z, modv.z); + return v; + } + + public Vector3i Sign() + { + Vector3i v = this; + v.x = Mathf.Sign(v.x); + v.y = Mathf.Sign(v.y); + v.z = Mathf.Sign(v.z); + return v; + } + + // Constants + private static readonly Vector3i _zero = new Vector3i(0, 0, 0); + private static readonly Vector3i _one = new Vector3i(1, 1, 1); + + private static readonly Vector3i _up = new Vector3i(0, 1, 0); + private static readonly Vector3i _down = new Vector3i(0, -1, 0); + private static readonly Vector3i _right = new Vector3i(1, 0, 0); + private static readonly Vector3i _left = new Vector3i(-1, 0, 0); + private static readonly Vector3i _forward = new Vector3i(0, 0, -1); + private static readonly Vector3i _back = new Vector3i(0, 0, 1); + + public static Vector3i Zero { get { return _zero; } } + public static Vector3i One { get { return _one; } } + + public static Vector3i Up { get { return _up; } } + public static Vector3i Down { get { return _down; } } + public static Vector3i Right { get { return _right; } } + public static Vector3i Left { get { return _left; } } + public static Vector3i Forward { get { return _forward; } } + public static Vector3i Back { get { return _back; } } + + // Constructors + public Vector3i(int x, int y, int z) + { + this.x = x; + this.y = y; + this.z = z; + } + public Vector3i(Vector3i vi) + { + this.x = vi.x; + this.y = vi.y; + this.z = vi.z; + } + public Vector3i(Vector3 v) + { + this.x = Mathf.RoundToInt(v.x); + this.y = Mathf.RoundToInt(v.y); + this.z = Mathf.RoundToInt(v.z); + } + + public static Vector3i operator +(Vector3i left, Vector3i right) + { + left.x += right.x; + left.y += right.y; + left.z += right.z; + return left; + } + + public static Vector3i operator -(Vector3i left, Vector3i right) + { + left.x -= right.x; + left.y -= right.y; + left.z -= right.z; + return left; + } + + public static Vector3i operator -(Vector3i vec) + { + vec.x = -vec.x; + vec.y = -vec.y; + vec.z = -vec.z; + return vec; + } + + public static Vector3i operator *(Vector3i vec, int scale) + { + vec.x *= scale; + vec.y *= scale; + vec.z *= scale; + return vec; + } + + public static Vector3i operator *(int scale, Vector3i vec) + { + vec.x *= scale; + vec.y *= scale; + vec.z *= scale; + return vec; + } + + public static Vector3i operator *(Vector3i left, Vector3i right) + { + left.x *= right.x; + left.y *= right.y; + left.z *= right.z; + return left; + } + + public static Vector3i operator /(Vector3i vec, int divisor) + { + vec.x /= divisor; + vec.y /= divisor; + vec.z /= divisor; + return vec; + } + + public static Vector3i operator /(Vector3i vec, Vector3i divisorv) + { + vec.x /= divisorv.x; + vec.y /= divisorv.y; + vec.z /= divisorv.z; + return vec; + } + + public static Vector3i operator %(Vector3i vec, int divisor) + { + vec.x %= divisor; + vec.y %= divisor; + vec.z %= divisor; + return vec; + } + + public static Vector3i operator %(Vector3i vec, Vector3i divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + vec.z %= divisorv.z; + return vec; + } + + public static Vector3i operator &(Vector3i vec, int and) + { + vec.x &= and; + vec.y &= and; + vec.z &= and; + return vec; + } + + public static Vector3i operator &(Vector3i vec, Vector3i andv) + { + vec.x &= andv.x; + vec.y &= andv.y; + vec.z &= andv.z; + return vec; + } + + public static bool operator ==(Vector3i left, Vector3i right) + { + return left.Equals(right); + } + + public static bool operator !=(Vector3i left, Vector3i right) + { + return !left.Equals(right); + } + + public static bool operator <(Vector3i left, Vector3i right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z < right.z; + else + return left.y < right.y; + } + + return left.x < right.x; + } + + public static bool operator >(Vector3i left, Vector3i right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z > right.z; + else + return left.y > right.y; + } + + return left.x > right.x; + } + + public static bool operator <=(Vector3i left, Vector3i right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z <= right.z; + else + return left.y < right.y; + } + + return left.x < right.x; + } + + public static bool operator >=(Vector3i left, Vector3i right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z >= right.z; + else + return left.y > right.y; + } + + return left.x > right.x; + } + + public static implicit operator Vector3(Vector3i value) + { + return new Vector3(value.x, value.y, value.z); + } + + public static explicit operator Vector3i(Vector3 value) + { + return new Vector3i(value); + } + + public override bool Equals(object obj) + { + if (obj is Vector3i) + { + return Equals((Vector3i)obj); + } + + return false; + } + + public bool Equals(Vector3i other) + { + return x == other.x && y == other.y && z == other.z; + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(), + this.y.ToString(), + this.z.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(format), + this.y.ToString(format), + this.z.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 5419cd06e6..ba0bbd7630 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -41,9 +41,11 @@ <Compile Include="Core\Attributes\SignalAttribute.cs" /> <Compile Include="Core\Attributes\ToolAttribute.cs" /> <Compile Include="Core\Basis.cs" /> + <Compile Include="Core\Callable.cs" /> <Compile Include="Core\Color.cs" /> <Compile Include="Core\Colors.cs" /> <Compile Include="Core\DebuggingUtils.cs" /> + <Compile Include="Core\DelegateUtils.cs" /> <Compile Include="Core\Dictionary.cs" /> <Compile Include="Core\Dispatcher.cs" /> <Compile Include="Core\DynamicObject.cs" /> @@ -65,13 +67,18 @@ <Compile Include="Core\Plane.cs" /> <Compile Include="Core\Quat.cs" /> <Compile Include="Core\Rect2.cs" /> + <Compile Include="Core\Rect2i.cs" /> <Compile Include="Core\RID.cs" /> + <Compile Include="Core\SignalInfo.cs" /> <Compile Include="Core\SignalAwaiter.cs" /> <Compile Include="Core\StringExtensions.cs" /> + <Compile Include="Core\StringName.cs" /> <Compile Include="Core\Transform.cs" /> <Compile Include="Core\Transform2D.cs" /> <Compile Include="Core\Vector2.cs" /> + <Compile Include="Core\Vector2i.cs" /> <Compile Include="Core\Vector3.cs" /> + <Compile Include="Core\Vector3i.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <!-- diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 8c77220b85..f370883320 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -51,7 +51,7 @@ Object *godot_icall_Object_Ctor(MonoObject *p_obj) { void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { #ifdef DEBUG_ENABLED - CRASH_COND(p_ptr == NULL); + CRASH_COND(p_ptr == nullptr); #endif if (p_ptr->get_script_instance()) { @@ -59,7 +59,7 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { if (cs_instance) { if (!cs_instance->is_destructing_script_instance()) { cs_instance->mono_object_disposed(p_obj); - p_ptr->set_script_instance(NULL); + p_ptr->set_script_instance(nullptr); } return; } @@ -70,8 +70,8 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { if (data) { CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); if (script_binding.inited) { - Ref<MonoGCHandle> &gchandle = script_binding.gchandle; - if (gchandle.is_valid()) { + MonoGCHandleData &gchandle = script_binding.gchandle; + if (!gchandle.is_released()) { CSharpLanguage::release_script_gchandle(p_obj, gchandle); } } @@ -80,7 +80,7 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer) { #ifdef DEBUG_ENABLED - CRASH_COND(p_ptr == NULL); + CRASH_COND(p_ptr == nullptr); // This is only called with Reference derived classes CRASH_COND(!Object::cast_to<Reference>(p_ptr)); #endif @@ -99,7 +99,7 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea if (delete_owner) { memdelete(ref); } else if (remove_script_instance) { - ref->set_script_instance(NULL); + ref->set_script_instance(nullptr); } } return; @@ -117,8 +117,8 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea if (data) { CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); if (script_binding.inited) { - Ref<MonoGCHandle> &gchandle = script_binding.gchandle; - if (gchandle.is_valid()) { + MonoGCHandleData &gchandle = script_binding.gchandle; + if (!gchandle.is_released()) { CSharpLanguage::release_script_gchandle(p_obj, gchandle); } } @@ -126,37 +126,44 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea } } -MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method) { - StringName type(GDMonoMarshal::mono_string_to_godot(p_type)); +void godot_icall_Object_ConnectEventSignals(Object *p_ptr) { + CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); + if (csharp_instance) { + csharp_instance->connect_event_signals(); + } +} + +MethodBind *godot_icall_Object_ClassDB_get_method(StringName *p_type, MonoString *p_method) { + StringName type = p_type ? *p_type : StringName(); StringName method(GDMonoMarshal::mono_string_to_godot(p_method)); return ClassDB::get_method(type, method); } -MonoObject *godot_icall_Object_weakref(Object *p_obj) { - if (!p_obj) - return NULL; +MonoObject *godot_icall_Object_weakref(Object *p_ptr) { + if (!p_ptr) + return nullptr; Ref<WeakRef> wref; - Reference *ref = Object::cast_to<Reference>(p_obj); + Reference *ref = Object::cast_to<Reference>(p_ptr); if (ref) { REF r = ref; if (!r.is_valid()) - return NULL; + return nullptr; wref.instance(); wref->set_ref(r); } else { wref.instance(); - wref->set_obj(p_obj); + wref->set_obj(p_ptr); } return GDMonoUtils::unmanaged_get_managed(wref.ptr()); } -Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter) { - String signal = GDMonoMarshal::mono_string_to_godot(p_signal); - return SignalAwaiterUtils::connect_signal_awaiter(p_source, signal, p_target, p_awaiter); +Error godot_icall_SignalAwaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, MonoObject *p_awaiter) { + StringName signal = p_signal ? *p_signal : StringName(); + return gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter); } MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) { @@ -223,10 +230,10 @@ MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString * MonoString *godot_icall_Object_ToString(Object *p_ptr) { #ifdef DEBUG_ENABLED // Cannot happen in C#; would get an ObjectDisposedException instead. - CRASH_COND(p_ptr == NULL); + CRASH_COND(p_ptr == nullptr); #endif - - String result = p_ptr->to_string(); + // Can't call 'Object::to_string()' here, as that can end up calling 'ToString' again resulting in an endless circular loop. + String result = "[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]"; return GDMonoMarshal::mono_string_from_godot(result); } diff --git a/modules/mono/glue/base_object_glue.h b/modules/mono/glue/base_object_glue.h index 22532dcff9..67769f3061 100644 --- a/modules/mono/glue/base_object_glue.h +++ b/modules/mono/glue/base_object_glue.h @@ -44,11 +44,13 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr); void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer); -MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method); +void godot_icall_Object_ConnectEventSignals(Object *p_ptr); -MonoObject *godot_icall_Object_weakref(Object *p_obj); +MethodBind *godot_icall_Object_ClassDB_get_method(StringName *p_type, MonoString *p_method); -Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter); +MonoObject *godot_icall_Object_weakref(Object *p_ptr); + +Error godot_icall_SignalAwaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, MonoObject *p_awaiter); // DynamicGodotObject diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index b7fa7fcab2..4e3dc9c4ea 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -49,7 +49,7 @@ void godot_icall_Array_Dtor(Array *ptr) { MonoObject *godot_icall_Array_At(Array *ptr, int index) { if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return NULL; + return nullptr; } return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index)); } @@ -57,7 +57,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()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return NULL; + return nullptr; } return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index), ManagedType(type_encoding, type_class)); } @@ -162,28 +162,28 @@ void godot_icall_Dictionary_Dtor(Dictionary *ptr) { MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) { Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == NULL) { + if (ret == nullptr) { MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr()); #ifdef DEBUG_ENABLED CRASH_COND(!exc); #endif GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException)); GDMonoUtils::set_pending_exception((MonoException *)exc); - return NULL; + return nullptr; } return GDMonoMarshal::variant_to_mono_object(ret); } MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class) { Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == NULL) { + if (ret == nullptr) { MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr()); #ifdef DEBUG_ENABLED CRASH_COND(!exc); #endif GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException)); GDMonoUtils::set_pending_exception((MonoException *)exc); - return NULL; + return nullptr; } return GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); } @@ -207,7 +207,7 @@ int godot_icall_Dictionary_Count(Dictionary *ptr) { void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value) { Variant varKey = GDMonoMarshal::mono_object_to_variant(key); Variant *ret = ptr->getptr(varKey); - if (ret != NULL) { + if (ret != nullptr) { GDMonoUtils::set_pending_exception(mono_get_exception_argument("key", "An element with the same key already exists")); return; } @@ -221,7 +221,7 @@ void godot_icall_Dictionary_Clear(Dictionary *ptr) { MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) { // no dupes Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - return ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value); + return ret != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value); } MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) { @@ -241,7 +241,7 @@ MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, Mono // no dupes Variant *ret = ptr->getptr(varKey); - if (ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value)) { + if (ret != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value)) { ptr->erase(varKey); return true; } @@ -251,8 +251,8 @@ MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, Mono MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) { Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == NULL) { - *value = NULL; + if (ret == nullptr) { + *value = nullptr; return false; } *value = GDMonoMarshal::variant_to_mono_object(ret); @@ -261,8 +261,8 @@ MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoBoolean godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class) { Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == NULL) { - *value = NULL; + if (ret == nullptr) { + *value = nullptr; return false; } *value = GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index cdacd90538..2da39a916a 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -45,7 +45,7 @@ MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects) { Variant ret; PackedByteArray varr = GDMonoMarshal::mono_array_to_PackedByteArray(p_bytes); - Error err = decode_variant(ret, varr.ptr(), varr.size(), NULL, p_allow_objects); + Error err = decode_variant(ret, varr.ptr(), varr.size(), nullptr, p_allow_objects); if (err != OK) { ret = RTR("Not enough bytes for decoding bytes, or invalid format."); } @@ -57,7 +57,7 @@ MonoObject *godot_icall_GD_convert(MonoObject *p_what, int32_t p_type) { const Variant *args[1] = { &what }; Callable::CallError ce; Variant ret = Variant::construct(Variant::Type(p_type), args, 1, ce); - ERR_FAIL_COND_V(ce.error != Callable::CallError::CALL_OK, NULL); + ERR_FAIL_COND_V(ce.error != Callable::CallError::CALL_OK, nullptr); return GDMonoMarshal::variant_to_mono_object(ret); } @@ -76,7 +76,7 @@ void godot_icall_GD_print(MonoArray *p_what) { for (int i = 0; i < length; i++) { MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - MonoException *exc = NULL; + MonoException *exc = nullptr; String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); if (exc) { @@ -98,7 +98,7 @@ void godot_icall_GD_printerr(MonoArray *p_what) { for (int i = 0; i < length; i++) { MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - MonoException *exc = NULL; + MonoException *exc = nullptr; String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); if (exc) { @@ -119,7 +119,7 @@ void godot_icall_GD_printraw(MonoArray *p_what) { for (int i = 0; i < length; i++) { MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - MonoException *exc = NULL; + MonoException *exc = nullptr; String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); if (exc) { @@ -140,7 +140,7 @@ void godot_icall_GD_prints(MonoArray *p_what) { for (int i = 0; i < length; i++) { MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - MonoException *exc = NULL; + MonoException *exc = nullptr; String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); if (exc) { @@ -164,7 +164,7 @@ void godot_icall_GD_printt(MonoArray *p_what) { for (int i = 0; i < length; i++) { MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - MonoException *exc = NULL; + MonoException *exc = nullptr; String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); if (exc) { @@ -241,8 +241,9 @@ MonoObject *godot_icall_GD_str2var(MonoString *p_str) { return GDMonoMarshal::variant_to_mono_object(ret); } -MonoBoolean godot_icall_GD_type_exists(MonoString *p_type) { - return ClassDB::class_exists(GDMonoMarshal::mono_string_to_godot(p_type)); +MonoBoolean godot_icall_GD_type_exists(StringName *p_type) { + StringName type = p_type ? *p_type : StringName(); + return ClassDB::class_exists(type); } void godot_icall_GD_pusherror(MonoString *p_str) { @@ -258,8 +259,8 @@ MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_object PackedByteArray barr; int len; - Error err = encode_variant(var, NULL, len, p_full_objects); - ERR_FAIL_COND_V_MSG(err != OK, NULL, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); + Error err = encode_variant(var, nullptr, len, p_full_objects); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); barr.resize(len); encode_variant(var, barr.ptrw(), len, p_full_objects); @@ -273,6 +274,10 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) { return GDMonoMarshal::mono_string_from_godot(vars); } +uint32_t godot_icall_TypeToVariantType(MonoReflectionType *p_refl_type) { + return (uint32_t)GDMonoMarshal::managed_to_variant_type(ManagedType::from_reftype(p_refl_type)); +} + MonoObject *godot_icall_DefaultGodotTaskScheduler() { return GDMonoCache::cached_data.task_scheduler_handle->get_target(); } @@ -300,6 +305,7 @@ void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists); mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes); mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str); + mono_add_internal_call("Godot.GD::godot_icall_TypeToVariantType", (void *)godot_icall_TypeToVariantType); // Dispatcher mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler); diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h index f00e2efc5d..3ad6058205 100644 --- a/modules/mono/glue/gd_glue.h +++ b/modules/mono/glue/gd_glue.h @@ -69,7 +69,7 @@ MonoString *godot_icall_GD_str(MonoArray *p_what); MonoObject *godot_icall_GD_str2var(MonoString *p_str); -MonoBoolean godot_icall_GD_type_exists(MonoString *p_type); +MonoBoolean godot_icall_GD_type_exists(StringName *p_type); MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_objects); diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h index 758b71f719..ee99a300b9 100644 --- a/modules/mono/glue/glue_header.h +++ b/modules/mono/glue/glue_header.h @@ -36,6 +36,7 @@ #include "nodepath_glue.h" #include "rid_glue.h" #include "string_glue.h" +#include "string_name_glue.h" /** * Registers internal calls that were not generated. This function is called @@ -44,6 +45,7 @@ void godot_register_glue_header_icalls() { godot_register_collections_icalls(); godot_register_gd_icalls(); + godot_register_string_name_icalls(); godot_register_nodepath_icalls(); godot_register_object_icalls(); godot_register_rid_icalls(); @@ -68,7 +70,7 @@ void godot_register_glue_header_icalls() { #include "../mono_gd/gd_mono_utils.h" #define GODOTSHARP_INSTANCE_OBJECT(m_instance, m_type) \ - static ClassDB::ClassInfo *ci = NULL; \ + static ClassDB::ClassInfo *ci = nullptr; \ if (!ci) { \ ci = ClassDB::classes.getptr(m_type); \ } \ diff --git a/modules/mono/glue/string_name_glue.cpp b/modules/mono/glue/string_name_glue.cpp new file mode 100644 index 0000000000..81006e5849 --- /dev/null +++ b/modules/mono/glue/string_name_glue.cpp @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* string_name_glue.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "string_name_glue.h" + +#ifdef MONO_GLUE_ENABLED + +#include "core/ustring.h" + +StringName *godot_icall_StringName_Ctor(MonoString *p_path) { + return memnew(StringName(GDMonoMarshal::mono_string_to_godot(p_path))); +} + +void godot_icall_StringName_Dtor(StringName *p_ptr) { + ERR_FAIL_NULL(p_ptr); + memdelete(p_ptr); +} + +MonoString *godot_icall_StringName_operator_String(StringName *p_np) { + return GDMonoMarshal::mono_string_from_godot(p_np->operator String()); +} + +MonoBoolean godot_icall_StringName_is_empty(StringName *p_ptr) { + return (MonoBoolean)(p_ptr == StringName()); +} + +void godot_register_string_name_icalls() { + mono_add_internal_call("Godot.StringName::godot_icall_StringName_Ctor", (void *)godot_icall_StringName_Ctor); + mono_add_internal_call("Godot.StringName::godot_icall_StringName_Dtor", (void *)godot_icall_StringName_Dtor); + mono_add_internal_call("Godot.StringName::godot_icall_StringName_operator_String", (void *)godot_icall_StringName_operator_String); + mono_add_internal_call("Godot.StringName::godot_icall_StringName_is_empty", (void *)godot_icall_StringName_is_empty); +} + +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_name_glue.h b/modules/mono/glue/string_name_glue.h new file mode 100644 index 0000000000..88354ddd84 --- /dev/null +++ b/modules/mono/glue/string_name_glue.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* string_name_glue.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef STRING_NAME_GLUE_H +#define STRING_NAME_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "core/string_name.h" + +#include "../mono_gd/gd_mono_marshal.h" + +StringName *godot_icall_StringName_Ctor(MonoString *p_path); + +void godot_icall_StringName_Dtor(StringName *p_ptr); + +MonoString *godot_icall_StringName_operator_String(StringName *p_np); + +MonoBoolean godot_icall_StringName_is_empty(StringName *p_ptr); + +// Register internal calls + +void godot_register_string_name_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // STRING_NAME_GLUE_H diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 47eb432490..fe8b925257 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -40,7 +40,7 @@ #endif #ifdef ANDROID_ENABLED -#include "mono_gd/gd_mono_android.h" +#include "mono_gd/support/mono-support.h" #endif #include "mono_gd/gd_mono.h" @@ -49,13 +49,13 @@ namespace GodotSharpDirs { String _get_expected_build_config() { #ifdef TOOLS_ENABLED - return "Tools"; + return "Debug"; #else #ifdef DEBUG_ENABLED - return "Debug"; + return "ExportDebug"; #else - return "Release"; + return "ExportRelease"; #endif #endif @@ -169,7 +169,7 @@ private: data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); #ifdef ANDROID_ENABLED - data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir(); + data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir(); #else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); #endif @@ -206,7 +206,7 @@ private: data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); #ifdef ANDROID_ENABLED - data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir(); + data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir(); #else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); data_game_assemblies_dir = data_dir_root.plus_file("Assemblies"); diff --git a/modules/mono/icons/icon_c_sharp_script.svg b/modules/mono/icons/CSharpScript.svg index 69664ca553..69664ca553 100644 --- a/modules/mono/icons/icon_c_sharp_script.svg +++ b/modules/mono/icons/CSharpScript.svg diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp new file mode 100644 index 0000000000..dfd78a8244 --- /dev/null +++ b/modules/mono/managed_callable.cpp @@ -0,0 +1,145 @@ +/*************************************************************************/ +/* managed_callable.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "managed_callable.h" + +#include "csharp_script.h" +#include "mono_gd/gd_mono_marshal.h" +#include "mono_gd/gd_mono_utils.h" + +#ifdef GD_MONO_HOT_RELOAD +SelfList<ManagedCallable>::List ManagedCallable::instances; +Map<ManagedCallable *, Array> ManagedCallable::instances_pending_reload; +Mutex ManagedCallable::instances_mutex; +#endif + +bool ManagedCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + const ManagedCallable *a = static_cast<const ManagedCallable *>(p_a); + const ManagedCallable *b = static_cast<const ManagedCallable *>(p_b); + + MonoDelegate *delegate_a = (MonoDelegate *)a->delegate_handle.get_target(); + MonoDelegate *delegate_b = (MonoDelegate *)b->delegate_handle.get_target(); + + if (!delegate_a || !delegate_b) { + if (!delegate_a && !delegate_b) + return true; + return false; + } + + // Call Delegate's 'Equals' + return GDMonoUtils::mono_delegate_equal(delegate_a, delegate_b); +} + +bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + if (compare_equal(p_a, p_b)) + return false; + return p_a < p_b; +} + +uint32_t ManagedCallable::hash() const { + // hmm + uint32_t hash = delegate_invoke->get_name().hash(); + return hash_djb2_one_64(delegate_handle.handle, hash); +} + +String ManagedCallable::get_as_text() const { + return "Delegate::Invoke"; +} + +CallableCustom::CompareEqualFunc ManagedCallable::get_compare_equal_func() const { + return compare_equal_func_ptr; +} + +CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const { + return compare_less_func_ptr; +} + +ObjectID ManagedCallable::get_object() const { + return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id(); +} + +void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better + r_return_value = Variant(); + +#ifdef GD_MONO_HOT_RELOAD + // Lost during hot-reload + ERR_FAIL_NULL(delegate_invoke); + ERR_FAIL_COND(delegate_handle.is_released()); +#endif + + ERR_FAIL_COND(delegate_invoke->get_parameters_count() < p_argcount); + + MonoObject *delegate = delegate_handle.get_target(); + + MonoException *exc = nullptr; + MonoObject *ret = delegate_invoke->invoke(delegate, p_arguments, &exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); + } else { + r_return_value = GDMonoMarshal::mono_object_to_variant(ret); + r_call_error.error = Callable::CallError::CALL_OK; + } +} + +void ManagedCallable::set_delegate(MonoDelegate *p_delegate) { + delegate_handle = MonoGCHandleData::new_strong_handle((MonoObject *)p_delegate); + MonoMethod *delegate_invoke_raw = mono_get_delegate_invoke(mono_object_get_class((MonoObject *)p_delegate)); + const StringName &delegate_invoke_name = CSharpLanguage::get_singleton()->get_string_names().delegate_invoke_method_name; + delegate_invoke = memnew(GDMonoMethod(delegate_invoke_name, delegate_invoke_raw)); // TODO: Use pooling for this GDMonoMethod instances +} + +ManagedCallable::ManagedCallable(MonoDelegate *p_delegate) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_delegate == nullptr); +#endif + + set_delegate(p_delegate); + +#ifdef GD_MONO_HOT_RELOAD + { + MutexLock lock(instances_mutex); + instances.add(&self_instance); + } +#endif +} + +ManagedCallable::~ManagedCallable() { +#ifdef GD_MONO_HOT_RELOAD + { + MutexLock lock(instances_mutex); + instances.remove(&self_instance); + instances_pending_reload.erase(this); + } +#endif + + delegate_handle.release(); +} diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h new file mode 100644 index 0000000000..4f71e14a2f --- /dev/null +++ b/modules/mono/managed_callable.h @@ -0,0 +1,77 @@ +/*************************************************************************/ +/* managed_callable.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MANAGED_CALLABLE_H +#define MANAGED_CALLABLE_H + +#include <mono/metadata/object.h> + +#include "core/callable.h" +#include "core/os/mutex.h" +#include "core/self_list.h" + +#include "mono_gc_handle.h" +#include "mono_gd/gd_mono_method.h" + +class ManagedCallable : public CallableCustom { + friend class CSharpLanguage; + MonoGCHandleData delegate_handle; + GDMonoMethod *delegate_invoke; + +#ifdef GD_MONO_HOT_RELOAD + SelfList<ManagedCallable> self_instance = this; + static SelfList<ManagedCallable>::List instances; + static Map<ManagedCallable *, Array> instances_pending_reload; + static Mutex instances_mutex; +#endif + +public: + uint32_t hash() const override; + String get_as_text() const override; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + ObjectID get_object() const override; + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + + _FORCE_INLINE_ MonoDelegate *get_delegate() { return (MonoDelegate *)delegate_handle.get_target(); } + + void set_delegate(MonoDelegate *p_delegate); + + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); + + static constexpr CompareEqualFunc compare_equal_func_ptr = &ManagedCallable::compare_equal; + static constexpr CompareEqualFunc compare_less_func_ptr = &ManagedCallable::compare_less; + + ManagedCallable(MonoDelegate *p_delegate); + ~ManagedCallable(); +}; + +#endif // MANAGED_CALLABLE_H diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp index feeea848ee..e362d5affb 100644 --- a/modules/mono/mono_gc_handle.cpp +++ b/modules/mono/mono_gc_handle.cpp @@ -32,56 +32,35 @@ #include "mono_gd/gd_mono.h" -uint32_t MonoGCHandle::new_strong_handle(MonoObject *p_object) { - - return mono_gchandle_new(p_object, /* pinned: */ false); -} - -uint32_t MonoGCHandle::new_strong_handle_pinned(MonoObject *p_object) { - - return mono_gchandle_new(p_object, /* pinned: */ true); -} - -uint32_t MonoGCHandle::new_weak_handle(MonoObject *p_object) { - - return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false); -} - -void MonoGCHandle::free_handle(uint32_t p_gchandle) { +void MonoGCHandleData::release() { +#ifdef DEBUG_ENABLED + CRASH_COND(handle && GDMono::get_singleton() == nullptr); +#endif - mono_gchandle_free(p_gchandle); + if (handle && GDMono::get_singleton()->is_runtime_initialized()) { + GDMonoUtils::free_gchandle(handle); + handle = 0; + } } -Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) { - - return memnew(MonoGCHandle(new_strong_handle(p_object), STRONG_HANDLE)); +MonoGCHandleData MonoGCHandleData::new_strong_handle(MonoObject *p_object) { + return MonoGCHandleData(GDMonoUtils::new_strong_gchandle(p_object), gdmono::GCHandleType::STRONG_HANDLE); } -Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) { - - return memnew(MonoGCHandle(new_weak_handle(p_object), WEAK_HANDLE)); +MonoGCHandleData MonoGCHandleData::new_strong_handle_pinned(MonoObject *p_object) { + return MonoGCHandleData(GDMonoUtils::new_strong_gchandle_pinned(p_object), gdmono::GCHandleType::STRONG_HANDLE); } -void MonoGCHandle::release() { - -#ifdef DEBUG_ENABLED - CRASH_COND(!released && GDMono::get_singleton() == NULL); -#endif - - if (!released && GDMono::get_singleton()->is_runtime_initialized()) { - free_handle(handle); - released = true; - } +MonoGCHandleData MonoGCHandleData::new_weak_handle(MonoObject *p_object) { + return MonoGCHandleData(GDMonoUtils::new_weak_gchandle(p_object), gdmono::GCHandleType::WEAK_HANDLE); } -MonoGCHandle::MonoGCHandle(uint32_t p_handle, HandleType p_handle_type) { +Ref<MonoGCHandleRef> MonoGCHandleRef::create_strong(MonoObject *p_object) { - released = false; - weak = p_handle_type == WEAK_HANDLE; - handle = p_handle; + return memnew(MonoGCHandleRef(MonoGCHandleData::new_strong_handle(p_object))); } -MonoGCHandle::~MonoGCHandle() { +Ref<MonoGCHandleRef> MonoGCHandleRef::create_weak(MonoObject *p_object) { - release(); + return memnew(MonoGCHandleRef(MonoGCHandleData::new_weak_handle(p_object))); } diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h index 37fc7d8a17..fbcb405b0d 100644 --- a/modules/mono/mono_gc_handle.h +++ b/modules/mono/mono_gc_handle.h @@ -35,42 +35,79 @@ #include "core/reference.h" -class MonoGCHandle : public Reference { +namespace gdmono { - GDCLASS(MonoGCHandle, Reference); +enum class GCHandleType : char { + NIL, + STRONG_HANDLE, + WEAK_HANDLE +}; + +} - bool released; - bool weak; +// Manual release of the GC handle must be done when using this struct +struct MonoGCHandleData { uint32_t handle; + gdmono::GCHandleType type; -public: - enum HandleType { - STRONG_HANDLE, - WEAK_HANDLE - }; + _FORCE_INLINE_ bool is_released() const { return !handle; } + _FORCE_INLINE_ bool is_weak() const { return type == gdmono::GCHandleType::WEAK_HANDLE; } - static uint32_t new_strong_handle(MonoObject *p_object); - static uint32_t new_strong_handle_pinned(MonoObject *p_object); - static uint32_t new_weak_handle(MonoObject *p_object); - static void free_handle(uint32_t p_gchandle); + _FORCE_INLINE_ MonoObject *get_target() const { return handle ? mono_gchandle_get_target(handle) : nullptr; } - static Ref<MonoGCHandle> create_strong(MonoObject *p_object); - static Ref<MonoGCHandle> create_weak(MonoObject *p_object); + void release(); - _FORCE_INLINE_ bool is_released() { return released; } - _FORCE_INLINE_ bool is_weak() { return weak; } + MonoGCHandleData &operator=(const MonoGCHandleData &p_other) { +#ifdef DEBUG_ENABLED + CRASH_COND(!is_released()); +#endif + handle = p_other.handle; + type = p_other.type; + return *this; + } - _FORCE_INLINE_ MonoObject *get_target() const { return released ? NULL : mono_gchandle_get_target(handle); } + MonoGCHandleData(const MonoGCHandleData &) = default; - _FORCE_INLINE_ void set_handle(uint32_t p_handle, HandleType p_handle_type) { - released = false; - weak = p_handle_type == WEAK_HANDLE; - handle = p_handle; + MonoGCHandleData() : + handle(0), + type(gdmono::GCHandleType::NIL) { } - void release(); - MonoGCHandle(uint32_t p_handle, HandleType p_handle_type); - ~MonoGCHandle(); + MonoGCHandleData(uint32_t p_handle, gdmono::GCHandleType p_type) : + handle(p_handle), + type(p_type) { + } + + static MonoGCHandleData new_strong_handle(MonoObject *p_object); + static MonoGCHandleData new_strong_handle_pinned(MonoObject *p_object); + static MonoGCHandleData new_weak_handle(MonoObject *p_object); +}; + +class MonoGCHandleRef : public Reference { + + GDCLASS(MonoGCHandleRef, Reference); + + MonoGCHandleData data; + +public: + static Ref<MonoGCHandleRef> create_strong(MonoObject *p_object); + static Ref<MonoGCHandleRef> create_weak(MonoObject *p_object); + + _FORCE_INLINE_ bool is_released() const { return data.is_released(); } + _FORCE_INLINE_ bool is_weak() const { return data.is_weak(); } + + _FORCE_INLINE_ MonoObject *get_target() const { return data.get_target(); } + + void release() { data.release(); } + + _FORCE_INLINE_ void set_handle(uint32_t p_handle, gdmono::GCHandleType p_handle_type) { + data = MonoGCHandleData(p_handle, p_handle_type); + } + + MonoGCHandleRef(const MonoGCHandleData &p_gc_handle_data) : + data(p_gc_handle_data) { + } + ~MonoGCHandleRef() { release(); } }; #endif // CSHARP_GC_HANDLE_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 895393537f..306a1997ab 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -37,6 +37,7 @@ #include <mono/metadata/mono-gc.h> #include <mono/metadata/profiler.h> +#include "core/debugger/engine_debugger.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" @@ -57,14 +58,25 @@ #ifdef ANDROID_ENABLED #include "android_mono_config.h" -#include "gd_mono_android.h" +#include "support/android_support.h" +#elif defined(IPHONE_ENABLED) +#include "support/ios_support.h" +#endif + +#if defined(TOOL_ENABLED) && defined(GD_MONO_SINGLE_APPDOMAIN) +// This will no longer be the case if we replace appdomains with AssemblyLoadContext +#error "Editor build requires support for multiple appdomains" +#endif + +#if defined(GD_MONO_HOT_RELOAD) && defined(GD_MONO_SINGLE_APPDOMAIN) +#error "Hot reloading requires multiple appdomains" #endif // TODO: // This has turn into a gigantic mess. There's too much going on here. Too much #ifdef as well. // It's just painful to read... It needs to be re-structured. Please, clean this up, future me. -GDMono *GDMono::singleton = NULL; +GDMono *GDMono::singleton = nullptr; namespace { @@ -177,7 +189,14 @@ MonoDomain *gd_initialize_mono_runtime() { gd_mono_debug_init(); #endif - return mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); +#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED) + // I don't know whether this actually matters or not + const char *runtime_version = "mobile"; +#else + const char *runtime_version = "v4.0.30319"; +#endif + + return mono_jit_init_version("GodotEngine.RootDomain", runtime_version); } #endif @@ -313,14 +332,22 @@ void GDMono::initialize() { determine_mono_dirs(assembly_rootdir, config_dir); // Leak if we call mono_set_dirs more than once - mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : NULL, - config_dir.length() ? config_dir.utf8().get_data() : NULL); + mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : nullptr, + config_dir.length() ? config_dir.utf8().get_data() : nullptr); add_mono_shared_libs_dir_to_path(); #endif +#ifdef ANDROID_ENABLED + mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data()); +#else + mono_config_parse(nullptr); +#endif + #if defined(ANDROID_ENABLED) - GDMonoAndroid::initialize(); + gdmono::android::support::initialize(); +#elif defined(IPHONE_ENABLED) + gdmono::ios::support::initialize(); #endif GDMonoAssembly::initialize(); @@ -329,13 +356,7 @@ void GDMono::initialize() { gd_mono_profiler_init(); #endif -#ifdef ANDROID_ENABLED - mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data()); -#else - mono_config_parse(NULL); -#endif - - mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL); + mono_install_unhandled_exception_hook(&unhandled_exception_hook, nullptr); #ifndef TOOLS_ENABLED // Exported games that don't use C# must still work. They likely don't ship with mscorlib. @@ -354,7 +375,7 @@ void GDMono::initialize() { #endif // NOTE: Internal calls must be registered after the Mono runtime initialization. - // Otherwise registration fails with the error: 'assertion 'hash != NULL' failed'. + // Otherwise registration fails with the error: 'assertion 'hash != nullptr' failed'. root_domain = gd_initialize_mono_runtime(); ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime."); @@ -370,15 +391,19 @@ void GDMono::initialize() { print_verbose("Mono: Runtime initialized"); #if defined(ANDROID_ENABLED) - GDMonoAndroid::register_internal_calls(); + gdmono::android::support::register_internal_calls(); #endif // mscorlib assembly MUST be present at initialization bool corlib_loaded = _load_corlib_assembly(); ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly."); +#ifndef GD_MONO_SINGLE_APPDOMAIN Error domain_load_err = _load_scripts_domain(); ERR_FAIL_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); +#else + scripts_domain = root_domain; +#endif _register_internal_calls(); @@ -490,11 +515,15 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) { assemblies[p_domain_id][p_assembly->get_name()] = p_assembly; } -GDMonoAssembly **GDMono::get_loaded_assembly(const String &p_name) { +GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) { + + if (p_name == "mscorlib") + return get_corlib_assembly(); MonoDomain *domain = mono_domain_get(); uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0; - return assemblies[domain_id].getptr(p_name); + GDMonoAssembly **result = assemblies[domain_id].getptr(p_name); + return result ? *result : nullptr; } bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) { @@ -516,7 +545,7 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); MonoImageOpenStatus status = MONO_IMAGE_OK; - MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly); + MonoAssembly *assembly = mono_assembly_load_full(p_aname, nullptr, &status, p_refonly); if (!assembly) return false; @@ -527,7 +556,7 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); - ERR_FAIL_COND_V(stored_assembly == NULL, false); + ERR_FAIL_COND_V(stored_assembly == nullptr, false); ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false); *r_assembly = *stored_assembly; @@ -548,14 +577,6 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMo if (!assembly) return false; -#ifdef DEBUG_ENABLED - uint32_t domain_id = mono_domain_get_id(mono_domain_get()); - GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); - - ERR_FAIL_COND_V(stored_assembly == NULL, false); - ERR_FAIL_COND_V(*stored_assembly != assembly, false); -#endif - *r_assembly = assembly; print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); @@ -575,15 +596,15 @@ ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMo if (nativecalls_klass) { GDMonoField *api_hash_field = nativecalls_klass->get_field("godot_api_hash"); if (api_hash_field) - api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(NULL)); + api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(nullptr)); GDMonoField *binds_ver_field = nativecalls_klass->get_field("bindings_version"); if (binds_ver_field) - api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(NULL)); + api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(nullptr)); GDMonoField *cs_glue_ver_field = nativecalls_klass->get_field("cs_glue_version"); if (cs_glue_ver_field) - api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(NULL)); + api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(nullptr)); } return api_assembly_version; @@ -714,7 +735,7 @@ bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config GDMono::LoadedApiAssembly temp_editor_api_assembly; if (!_try_load_api_assemblies(temp_core_api_assembly, temp_editor_api_assembly, - p_config, /* refonly: */ true, /* loaded_callback: */ NULL)) { + p_config, /* refonly: */ true, /* loaded_callback: */ nullptr)) { return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync; } @@ -893,8 +914,8 @@ void GDMono::_load_api_assemblies() { bool api_assemblies_loaded = _try_load_api_assemblies_preset(); +#if defined(TOOLS_ENABLED) && !defined(GD_MONO_SINGLE_APPDOMAIN) if (!api_assemblies_loaded) { -#ifdef TOOLS_ENABLED // The API assemblies are out of sync or some other error happened. Fine, try one more time, but // this time update them from the prebuilt assemblies directory before trying to load them again. @@ -915,8 +936,8 @@ void GDMono::_load_api_assemblies() { // 4. Try loading the updated assemblies api_assemblies_loaded = _try_load_api_assemblies_preset(); -#endif } +#endif if (!api_assemblies_loaded) { // welp... too bad @@ -981,8 +1002,8 @@ void GDMono::_install_trace_listener() { GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils"); GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener"); - MonoException *exc = NULL; - install_func->invoke_raw(NULL, NULL, &exc); + MonoException *exc = nullptr; + install_func->invoke_raw(nullptr, nullptr, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener."); @@ -990,9 +1011,10 @@ void GDMono::_install_trace_listener() { #endif } +#ifndef GD_MONO_SINGLE_APPDOMAIN Error GDMono::_load_scripts_domain() { - ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG); + ERR_FAIL_COND_V(scripts_domain != nullptr, ERR_BUG); print_verbose("Mono: Loading scripts domain..."); @@ -1009,7 +1031,7 @@ Error GDMono::_unload_scripts_domain() { ERR_FAIL_NULL_V(scripts_domain, ERR_BUG); - print_verbose("Mono: Unloading scripts domain..."); + print_verbose("Mono: Finalizing scripts domain..."); if (mono_domain_get() != root_domain) mono_domain_set(root_domain, true); @@ -1028,21 +1050,23 @@ Error GDMono::_unload_scripts_domain() { _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); - core_api_assembly.assembly = NULL; + core_api_assembly.assembly = nullptr; #ifdef TOOLS_ENABLED - editor_api_assembly.assembly = NULL; + editor_api_assembly.assembly = nullptr; #endif - project_assembly = NULL; + project_assembly = nullptr; #ifdef TOOLS_ENABLED - tools_assembly = NULL; - tools_project_editor_assembly = NULL; + tools_assembly = nullptr; + tools_project_editor_assembly = nullptr; #endif MonoDomain *domain = scripts_domain; - scripts_domain = NULL; + scripts_domain = nullptr; + + print_verbose("Mono: Unloading scripts domain..."); - MonoException *exc = NULL; + MonoException *exc = nullptr; mono_domain_try_unload(domain, (MonoObject **)&exc); if (exc) { @@ -1053,6 +1077,7 @@ Error GDMono::_unload_scripts_domain() { return OK; } +#endif #ifdef GD_MONO_HOT_RELOAD Error GDMono::reload_scripts_domain() { @@ -1091,9 +1116,10 @@ Error GDMono::reload_scripts_domain() { } #endif +#ifndef GD_MONO_SINGLE_APPDOMAIN Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { - CRASH_COND(p_domain == NULL); + CRASH_COND(p_domain == nullptr); CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead String domain_name = mono_domain_get_friendly_name(p_domain); @@ -1111,7 +1137,7 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { _domain_assemblies_cleanup(mono_domain_get_id(p_domain)); - MonoException *exc = NULL; + MonoException *exc = nullptr; mono_domain_try_unload(p_domain, (MonoObject **)&exc); if (exc) { @@ -1122,6 +1148,7 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { return OK; } +#endif GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { @@ -1133,7 +1160,7 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { uint32_t domain_id = mono_domain_get_id(mono_domain_get()); HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; - const String *k = NULL; + const String *k = nullptr; while ((k = domain_assemblies.next(k))) { GDMonoAssembly *assembly = domain_assemblies.get(*k); if (assembly->get_image() == image) { @@ -1144,30 +1171,34 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { } } - return NULL; + return nullptr; } GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) { + GDMonoClass *klass = corlib_assembly->get_class(p_namespace, p_name); + if (klass) + return klass; + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; - const String *k = NULL; + const String *k = nullptr; while ((k = domain_assemblies.next(k))) { GDMonoAssembly *assembly = domain_assemblies.get(*k); - GDMonoClass *klass = assembly->get_class(p_namespace, p_name); + klass = assembly->get_class(p_namespace, p_name); if (klass) return klass; } - return NULL; + return nullptr; } void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) { HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id]; - const String *k = NULL; + const String *k = nullptr; while ((k = domain_assemblies.next(k))) { memdelete(domain_assemblies.get(*k)); } @@ -1183,8 +1214,8 @@ void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) { #ifdef DEBUG_ENABLED GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); - if (ScriptDebugger::get_singleton()) - ScriptDebugger::get_singleton()->idle_poll(); + if (EngineDebugger::is_active()) + EngineDebugger::get_singleton()->poll_events(false); #endif exit(mono_environment_exitcode_get()); @@ -1201,14 +1232,14 @@ GDMono::GDMono() { runtime_initialized = false; finalizing_scripts_domain = false; - root_domain = NULL; - scripts_domain = NULL; + root_domain = nullptr; + scripts_domain = nullptr; - corlib_assembly = NULL; - project_assembly = NULL; + corlib_assembly = nullptr; + project_assembly = nullptr; #ifdef TOOLS_ENABLED - tools_assembly = NULL; - tools_project_editor_assembly = NULL; + tools_assembly = nullptr; + tools_project_editor_assembly = nullptr; #endif api_core_hash = 0; @@ -1222,18 +1253,50 @@ GDMono::GDMono() { GDMono::~GDMono() { if (is_runtime_initialized()) { +#ifndef GD_MONO_SINGLE_APPDOMAIN if (scripts_domain) { Error err = _unload_scripts_domain(); if (err != OK) { ERR_PRINT("Mono: Failed to unload scripts domain."); } } +#else + CRASH_COND(scripts_domain != root_domain); + + print_verbose("Mono: Finalizing scripts domain..."); + + if (mono_domain_get() != root_domain) + mono_domain_set(root_domain, true); + + finalizing_scripts_domain = true; + + if (!mono_domain_finalize(root_domain, 2000)) { + ERR_PRINT("Mono: Domain finalization timeout."); + } + + finalizing_scripts_domain = false; - const uint32_t *k = NULL; + mono_gc_collect(mono_gc_max_generation()); + + GDMonoCache::clear_godot_api_cache(); + + _domain_assemblies_cleanup(mono_domain_get_id(root_domain)); + + core_api_assembly.assembly = nullptr; + + project_assembly = nullptr; + + root_domain = nullptr; + scripts_domain = nullptr; + + // Leave the rest to 'mono_jit_cleanup' +#endif + + const uint32_t *k = nullptr; while ((k = assemblies.next(k))) { HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies.get(*k); - const String *kk = NULL; + const String *kk = nullptr; while ((kk = domain_assemblies.next(kk))) { memdelete(domain_assemblies.get(*kk)); } @@ -1244,22 +1307,22 @@ GDMono::~GDMono() { mono_jit_cleanup(root_domain); -#if defined(ANDROID_ENABLED) - GDMonoAndroid::cleanup(); -#endif - print_verbose("Mono: Finalized"); runtime_initialized = false; } +#if defined(ANDROID_ENABLED) + gdmono::android::support::cleanup(); +#endif + if (gdmono_log) memdelete(gdmono_log); - singleton = NULL; + singleton = nullptr; } -_GodotSharp *_GodotSharp::singleton = NULL; +_GodotSharp *_GodotSharp::singleton = nullptr; void _GodotSharp::attach_thread() { @@ -1287,7 +1350,7 @@ int32_t _GodotSharp::get_scripts_domain_id() { bool _GodotSharp::is_scripts_domain_loaded() { - return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != NULL; + return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != nullptr; } bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { @@ -1352,5 +1415,5 @@ _GodotSharp::_GodotSharp() { _GodotSharp::~_GodotSharp() { - singleton = NULL; + singleton = nullptr; } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 306fa15f12..4898833e8e 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -91,7 +91,7 @@ public: bool out_of_sync; LoadedApiAssembly() : - assembly(NULL), + assembly(nullptr), out_of_sync(false) { } }; @@ -105,7 +105,7 @@ private: MonoDomain *root_domain; MonoDomain *scripts_domain; - HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies; + HashMap<uint32_t, HashMap<String, GDMonoAssembly *>> assemblies; GDMonoAssembly *corlib_assembly; GDMonoAssembly *project_assembly; @@ -144,8 +144,10 @@ private: void _register_internal_calls(); +#ifndef GD_MONO_SINGLE_APPDOMAIN Error _load_scripts_domain(); Error _unload_scripts_domain(); +#endif void _domain_assemblies_cleanup(uint32_t p_domain_id); @@ -198,18 +200,18 @@ public: #ifdef TOOLS_ENABLED bool copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config); - String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = NULL, const bool *p_editor_api_out_of_sync = NULL); + String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = nullptr, const bool *p_editor_api_out_of_sync = nullptr); #endif static GDMono *get_singleton() { return singleton; } - GD_NORETURN static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); + [[noreturn]] static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); UnhandledExceptionPolicy get_unhandled_exception_policy() const { return unhandled_exception_policy; } // Do not use these, unless you know what you're doing void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly); - GDMonoAssembly **get_loaded_assembly(const String &p_name); + GDMonoAssembly *get_loaded_assembly(const String &p_name); _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; } @@ -263,7 +265,7 @@ public: this->prev_domain = prev_domain; mono_domain_set(p_domain, false); } else { - this->prev_domain = NULL; + this->prev_domain = nullptr; } } diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 6cf5377e2c..8439769d84 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -42,9 +42,6 @@ #include "gd_mono_cache.h" #include "gd_mono_class.h" -bool GDMonoAssembly::no_search = false; -bool GDMonoAssembly::in_preload = false; - Vector<String> GDMonoAssembly::search_dirs; void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) { @@ -78,7 +75,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin if (p_custom_config.empty()) { r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); } else { - String api_config = p_custom_config == "Release" ? "Release" : "Debug"; + String api_config = p_custom_config == "ExportRelease" ? "Release" : "Debug"; r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config)); } @@ -94,19 +91,30 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin #endif } +// This is how these assembly loading hooks work: +// +// - The 'search' hook checks if the assembly has already been loaded, to avoid loading again. +// - The 'preload' hook does the actual loading and is only called if the +// 'search' hook didn't find the assembly in the list of loaded assemblies. +// - The 'load' hook is called after the assembly has been loaded. Its job is to add the +// assembly to the list of loaded assemblies so that the 'search' hook can look it up. + void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) { - if (no_search) - return; - - // If our search and preload hooks fail to load the assembly themselves, the mono runtime still might. - // Just do Assembly.LoadFrom("/Full/Path/On/Disk.dll"); - // In this case, we wouldn't have the assembly known in GDMono, which causes crashes - // if any class inside the assembly is looked up by Godot. - // And causing a lookup like that is as easy as throwing an exception defined in it... - // No, we can't make the assembly load hooks smart enough because they get passed a MonoAssemblyName* only, - // not the disk path passed to say Assembly.LoadFrom(). - _wrap_mono_assembly(assembly); + String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); + + MonoImage *image = mono_assembly_get_image(assembly); + + GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, image, assembly)); + +#ifdef GD_MONO_HOT_RELOAD + const char *path = mono_image_get_filename(image); + if (FileAccess::exists(path)) + gdassembly->modified_time = FileAccess::get_modified_time(path); +#endif + + MonoDomain *domain = mono_domain_get(); + GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly); } MonoAssembly *GDMonoAssembly::assembly_search_hook(MonoAssemblyName *aname, void *user_data) { @@ -132,71 +140,24 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d String name = String::utf8(mono_assembly_name_get_name(aname)); bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); - if (no_search) - return NULL; - - GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); + GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); if (loaded_asm) - return (*loaded_asm)->get_assembly(); - - no_search = true; // Avoid the recursion madness - - GDMonoAssembly *res = _load_assembly_search(name, search_dirs, refonly); - - no_search = false; + return loaded_asm->get_assembly(); - return res ? res->get_assembly() : NULL; + return nullptr; } -static _THREAD_LOCAL_(MonoImage *) image_corlib_loading = NULL; - MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) { (void)user_data; // UNUSED - { - // If we find the assembly here, we load it with 'mono_assembly_load_from_full', - // which in turn invokes load hooks before returning the MonoAssembly to us. - // One of the load hooks is 'load_aot_module'. This hook can end up calling preload hooks - // again for the same assembly in certain in certain circumstances (the 'do_load_image' part). - // If this is the case and we return NULL due to the no_search condition below, - // it will result in an internal crash later on. Therefore we need to return the assembly we didn't - // get yet from 'mono_assembly_load_from_full'. Luckily we have the image, which already got it. - // This must be done here. If done in search hooks, it would cause 'mono_assembly_load_from_full' - // to think another MonoAssembly for this assembly was already loaded, making it delete its own, - // when in fact both pointers were the same... This hooks thing is confusing. - if (image_corlib_loading) { - return mono_image_get_assembly(image_corlib_loading); - } - } - - if (no_search) - return NULL; - - no_search = true; - in_preload = true; - String name = String::utf8(mono_assembly_name_get_name(aname)); - bool has_extension = name.ends_with(".dll"); - - GDMonoAssembly *res = NULL; - if (has_extension ? name == "mscorlib.dll" : name == "mscorlib") { - GDMonoAssembly **stored_assembly = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); - if (stored_assembly) - return (*stored_assembly)->get_assembly(); - - res = _load_assembly_search("mscorlib.dll", search_dirs, refonly); - } - - no_search = false; - in_preload = false; - - return res ? res->get_assembly() : NULL; + return _load_assembly_search(name, search_dirs, refonly); } -GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) { +MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) { - GDMonoAssembly *res = NULL; + MonoAssembly *res = nullptr; String path; bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe"); @@ -207,28 +168,28 @@ GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, cons if (has_extension) { path = search_dir.plus_file(p_name); if (FileAccess::exists(path)) { - res = _load_assembly_from(p_name.get_basename(), path, p_refonly); - if (res != NULL) + res = _real_load_assembly_from(path, p_refonly); + if (res != nullptr) return res; } } else { path = search_dir.plus_file(p_name + ".dll"); if (FileAccess::exists(path)) { - res = _load_assembly_from(p_name, path, p_refonly); - if (res != NULL) + res = _real_load_assembly_from(path, p_refonly); + if (res != nullptr) return res; } path = search_dir.plus_file(p_name + ".exe"); if (FileAccess::exists(path)) { - res = _load_assembly_from(p_name, path, p_refonly); - if (res != NULL) + res = _real_load_assembly_from(path, p_refonly); + if (res != nullptr) return res; } } } - return NULL; + return nullptr; } String GDMonoAssembly::find_assembly(const String &p_name) { @@ -258,91 +219,50 @@ String GDMonoAssembly::find_assembly(const String &p_name) { return String(); } -GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly) { - - GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path)); - - Error err = assembly->load(p_refonly); - - if (err != OK) { - memdelete(assembly); - ERR_FAIL_V(NULL); - } - - MonoDomain *domain = mono_domain_get(); - GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, assembly); - - return assembly; -} - -void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) { - String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); - - MonoImage *image = mono_assembly_get_image(assembly); - - GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, mono_image_get_filename(image))); - Error err = gdassembly->wrapper_for_image(image); - - if (err != OK) { - memdelete(gdassembly); - ERR_FAIL(); - } - - MonoDomain *domain = mono_domain_get(); - GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly); -} - void GDMonoAssembly::initialize() { fill_search_dirs(search_dirs); - mono_install_assembly_search_hook(&assembly_search_hook, NULL); - mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, NULL); - mono_install_assembly_preload_hook(&assembly_preload_hook, NULL); - mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, NULL); - mono_install_assembly_load_hook(&assembly_load_hook, NULL); + mono_install_assembly_search_hook(&assembly_search_hook, nullptr); + mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, nullptr); + mono_install_assembly_preload_hook(&assembly_preload_hook, nullptr); + mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, nullptr); + mono_install_assembly_load_hook(&assembly_load_hook, nullptr); } -Error GDMonoAssembly::load(bool p_refonly) { - - ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE); - - refonly = p_refonly; +MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) { - uint64_t last_modified_time = FileAccess::get_modified_time(path); - - Vector<uint8_t> data = FileAccess::get_file_as_array(path); - ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ); + Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); + ERR_FAIL_COND_V_MSG(data.empty(), nullptr, "Could read the assembly in the specified location"); String image_filename; #ifdef ANDROID_ENABLED - if (path.begins_with("res://")) { - image_filename = path.substr(6, path.length()); + if (p_path.begins_with("res://")) { + image_filename = p_path.substr(6, p_path.length()); } else { - image_filename = ProjectSettings::get_singleton()->globalize_path(path); + image_filename = ProjectSettings::get_singleton()->globalize_path(p_path); } #else // FIXME: globalize_path does not work on exported games - image_filename = ProjectSettings::get_singleton()->globalize_path(path); + image_filename = ProjectSettings::get_singleton()->globalize_path(p_path); #endif MonoImageOpenStatus status = MONO_IMAGE_OK; - image = mono_image_open_from_data_with_name( + MonoImage *image = mono_image_open_from_data_with_name( (char *)&data[0], data.size(), - true, &status, refonly, - image_filename.utf8().get_data()); + true, &status, p_refonly, + image_filename.utf8()); - ERR_FAIL_COND_V(status != MONO_IMAGE_OK, ERR_FILE_CANT_OPEN); - ERR_FAIL_NULL_V(image, ERR_FILE_CANT_OPEN); + ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from the loaded data"); #ifdef DEBUG_ENABLED Vector<uint8_t> pdb_data; - String pdb_path(path + ".pdb"); + String pdb_path(p_path + ".pdb"); if (!FileAccess::exists(pdb_path)) { - pdb_path = path.get_basename() + ".pdb"; // without .dll + pdb_path = p_path.get_basename() + ".pdb"; // without .dll if (!FileAccess::exists(pdb_path)) goto no_pdb; @@ -357,44 +277,21 @@ no_pdb: #endif - bool is_corlib_preload = in_preload && name == "mscorlib"; - - if (is_corlib_preload) - image_corlib_loading = image; + status = MONO_IMAGE_OK; - assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, refonly); + MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly); - if (is_corlib_preload) - image_corlib_loading = NULL; - - ERR_FAIL_COND_V(status != MONO_IMAGE_OK || assembly == NULL, ERR_FILE_CANT_OPEN); + ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, nullptr, "Failed to load assembly for image"); // Decrement refcount which was previously incremented by mono_image_open_from_data_with_name mono_image_close(image); - loaded = true; - modified_time = last_modified_time; - - return OK; -} - -Error GDMonoAssembly::wrapper_for_image(MonoImage *p_image) { - - ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE); - - assembly = mono_image_get_assembly(p_image); - ERR_FAIL_NULL_V(assembly, FAILED); - - image = p_image; - - loaded = true; - - return OK; + return assembly; } void GDMonoAssembly::unload() { - ERR_FAIL_COND(!loaded); + ERR_FAIL_NULL(image); // Should not be called if already unloaded for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) { memdelete(E->value()); @@ -403,14 +300,17 @@ void GDMonoAssembly::unload() { cached_classes.clear(); cached_raw.clear(); - assembly = NULL; - image = NULL; - loaded = false; + assembly = nullptr; + image = nullptr; +} + +String GDMonoAssembly::get_path() const { + return String::utf8(mono_image_get_filename(image)); } GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) { - ERR_FAIL_COND_V(!loaded, NULL); + ERR_FAIL_NULL_V(image, nullptr); ClassKey key(p_namespace, p_name); @@ -422,7 +322,7 @@ GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const Stri MonoClass *mono_class = mono_class_from_name(image, String(p_namespace).utf8(), String(p_name).utf8()); if (!mono_class) - return NULL; + return nullptr; GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this)); @@ -434,7 +334,7 @@ GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const Stri GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) { - ERR_FAIL_COND_V(!loaded, NULL); + ERR_FAIL_NULL_V(image, nullptr); Map<MonoClass *, GDMonoClass *>::Element *match = cached_raw.find(p_mono_class); @@ -454,7 +354,7 @@ GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) { GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) { - GDMonoClass *match = NULL; + GDMonoClass *match = nullptr; if (gdobject_class_cache_updated) { Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class); @@ -486,7 +386,7 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) GDMonoClass *current_nested = nested_classes.front()->get(); nested_classes.pop_back(); - void *iter = NULL; + void *iter = nullptr; while (true) { MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter); @@ -514,32 +414,38 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) { - GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); - if (loaded_asm) - return *loaded_asm; -#ifdef DEBUG_ENABLED - CRASH_COND(!FileAccess::exists(p_path)); -#endif - no_search = true; - GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly); - no_search = false; - return res; -} + if (p_name == "mscorlib" || p_name == "mscorlib.dll") + return GDMono::get_singleton()->get_corlib_assembly(); -GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) { + // We need to manually call the search hook in this case, as it won't be called in the next step + MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); + MonoAssembly *assembly = mono_assembly_invoke_search_hook(aname); + mono_assembly_name_free(aname); + mono_free(aname); + + if (!assembly) { + assembly = _real_load_assembly_from(p_path, p_refonly); + ERR_FAIL_NULL_V(assembly, nullptr); + } - loaded = false; - gdobject_class_cache_updated = false; - name = p_name; - path = p_path; - refonly = false; - modified_time = 0; - assembly = NULL; - image = NULL; + GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); + ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?"); + + return loaded_asm; +} + +GDMonoAssembly::GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) : + name(p_name), + image(p_image), + assembly(p_assembly), +#ifdef GD_MONO_HOT_RELOAD + modified_time(0), +#endif + gdobject_class_cache_updated(false) { } GDMonoAssembly::~GDMonoAssembly() { - if (loaded) + if (image) unload(); } diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 4740e10339..43c8225b74 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -68,24 +68,20 @@ class GDMonoAssembly { StringName class_name; }; - MonoAssembly *assembly; + String name; MonoImage *image; + MonoAssembly *assembly; - bool refonly; - bool loaded; - - String name; - String path; +#ifdef GD_MONO_HOT_RELOAD uint64_t modified_time; - - HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes; - Map<MonoClass *, GDMonoClass *> cached_raw; +#endif bool gdobject_class_cache_updated; Map<StringName, GDMonoClass *> gdobject_class_cache; - static bool no_search; - static bool in_preload; + HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes; + Map<MonoClass *, GDMonoClass *> cached_raw; + static Vector<String> search_dirs; static void assembly_load_hook(MonoAssembly *assembly, void *user_data); @@ -97,25 +93,24 @@ class GDMonoAssembly { static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly); static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly); - static GDMonoAssembly *_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly); - static GDMonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly); - static void _wrap_mono_assembly(MonoAssembly *assembly); + static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly); + static MonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly); friend class GDMono; static void initialize(); public: - Error load(bool p_refonly); - Error wrapper_for_image(MonoImage *p_image); void unload(); - _FORCE_INLINE_ bool is_refonly() const { return refonly; } - _FORCE_INLINE_ bool is_loaded() const { return loaded; } _FORCE_INLINE_ MonoImage *get_image() const { return image; } _FORCE_INLINE_ MonoAssembly *get_assembly() const { return assembly; } _FORCE_INLINE_ String get_name() const { return name; } - _FORCE_INLINE_ String get_path() const { return path; } + +#ifdef GD_MONO_HOT_RELOAD _FORCE_INLINE_ uint64_t get_modified_time() const { return modified_time; } +#endif + + String get_path() const; GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); GDMonoClass *get_class(MonoClass *p_mono_class); @@ -128,7 +123,7 @@ public: static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); - GDMonoAssembly(const String &p_name, const String &p_path = String()); + GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly); ~GDMonoAssembly(); }; diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 0ad90a510e..facc0da780 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -40,11 +40,11 @@ namespace GDMonoCache { CachedData cached_data; -#define CACHE_AND_CHECK(m_var, m_val) \ - { \ - CRASH_COND(m_var != NULL); \ - m_var = m_val; \ - ERR_FAIL_COND_MSG(m_var == NULL, "Mono Cache: Member " #m_var " is null."); \ +#define CACHE_AND_CHECK(m_var, m_val) \ + { \ + CRASH_COND(m_var != nullptr); \ + m_var = m_val; \ + ERR_FAIL_COND_MSG(m_var == nullptr, "Mono Cache: Member " #m_var " is null."); \ } #define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(cached_data.class_##m_class, m_val) @@ -54,12 +54,12 @@ CachedData cached_data; #define CACHE_METHOD_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(cached_data.method_##m_class##_##m_method, m_val) #define CACHE_PROPERTY_AND_CHECK(m_class, m_property, m_val) CACHE_AND_CHECK(cached_data.property_##m_class##_##m_property, m_val) -#define CACHE_METHOD_THUNK_AND_CHECK_IMPL(m_var, m_val) \ - { \ - CRASH_COND(!m_var.is_null()); \ - ERR_FAIL_COND_MSG(m_val == NULL, "Mono Cache: Method for member " #m_var " is null."); \ - m_var.set_from_method(m_val); \ - ERR_FAIL_COND_MSG(m_var.is_null(), "Mono Cache: Member " #m_var " is null."); \ +#define CACHE_METHOD_THUNK_AND_CHECK_IMPL(m_var, m_val) \ + { \ + CRASH_COND(!m_var.is_null()); \ + ERR_FAIL_COND_MSG(m_val == nullptr, "Mono Cache: Method for member " #m_var " is null."); \ + m_var.set_from_method(m_val); \ + ERR_FAIL_COND_MSG(m_var.is_null(), "Mono Cache: Member " #m_var " is null."); \ } #define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_METHOD_THUNK_AND_CHECK_IMPL(cached_data.methodthunk_##m_class##_##m_method, m_val) @@ -68,94 +68,105 @@ void CachedData::clear_corlib_cache() { corlib_cache_updated = false; - class_MonoObject = NULL; - class_bool = NULL; - class_int8_t = NULL; - class_int16_t = NULL; - class_int32_t = NULL; - class_int64_t = NULL; - class_uint8_t = NULL; - class_uint16_t = NULL; - class_uint32_t = NULL; - class_uint64_t = NULL; - class_float = NULL; - class_double = NULL; - class_String = NULL; - class_IntPtr = NULL; - - class_System_Collections_IEnumerable = NULL; - class_System_Collections_IDictionary = NULL; + class_MonoObject = nullptr; + class_bool = nullptr; + class_int8_t = nullptr; + class_int16_t = nullptr; + class_int32_t = nullptr; + class_int64_t = nullptr; + class_uint8_t = nullptr; + class_uint16_t = nullptr; + class_uint32_t = nullptr; + class_uint64_t = nullptr; + class_float = nullptr; + class_double = nullptr; + class_String = nullptr; + class_IntPtr = nullptr; + + class_System_Collections_IEnumerable = nullptr; + class_System_Collections_IDictionary = nullptr; #ifdef DEBUG_ENABLED - class_System_Diagnostics_StackTrace = NULL; + class_System_Diagnostics_StackTrace = nullptr; methodthunk_System_Diagnostics_StackTrace_GetFrames.nullify(); - method_System_Diagnostics_StackTrace_ctor_bool = NULL; - method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL; + method_System_Diagnostics_StackTrace_ctor_bool = nullptr; + method_System_Diagnostics_StackTrace_ctor_Exception_bool = nullptr; #endif - class_KeyNotFoundException = NULL; + class_KeyNotFoundException = nullptr; } void CachedData::clear_godot_api_cache() { godot_api_cache_updated = false; - rawclass_Dictionary = NULL; - - class_Vector2 = NULL; - class_Rect2 = NULL; - class_Transform2D = NULL; - class_Vector3 = NULL; - class_Basis = NULL; - class_Quat = NULL; - class_Transform = NULL; - class_AABB = NULL; - class_Color = NULL; - class_Plane = NULL; - class_NodePath = NULL; - class_RID = NULL; - class_GodotObject = NULL; - class_GodotResource = NULL; - class_Node = NULL; - class_Control = NULL; - class_Spatial = NULL; - class_WeakRef = NULL; - class_Array = NULL; - class_Dictionary = NULL; - class_MarshalUtils = NULL; - class_ISerializationListener = NULL; + rawclass_Dictionary = nullptr; + + class_Vector2 = nullptr; + class_Vector2i = nullptr; + class_Rect2 = nullptr; + class_Rect2i = nullptr; + class_Transform2D = nullptr; + class_Vector3 = nullptr; + class_Vector3i = nullptr; + class_Basis = nullptr; + class_Quat = nullptr; + class_Transform = nullptr; + class_AABB = nullptr; + class_Color = nullptr; + class_Plane = nullptr; + class_StringName = nullptr; + class_NodePath = nullptr; + class_RID = nullptr; + class_GodotObject = nullptr; + class_GodotResource = nullptr; + class_Node = nullptr; + class_Control = nullptr; + class_Node3D = nullptr; + class_WeakRef = nullptr; + class_Callable = nullptr; + class_SignalInfo = nullptr; + class_Array = nullptr; + class_Dictionary = nullptr; + class_MarshalUtils = nullptr; + class_ISerializationListener = nullptr; #ifdef DEBUG_ENABLED - class_DebuggingUtils = NULL; + class_DebuggingUtils = nullptr; methodthunk_DebuggingUtils_GetStackFrameInfo.nullify(); #endif - class_ExportAttribute = NULL; - field_ExportAttribute_hint = NULL; - field_ExportAttribute_hintString = NULL; - class_SignalAttribute = NULL; - class_ToolAttribute = NULL; - class_RemoteAttribute = NULL; - class_MasterAttribute = NULL; - class_PuppetAttribute = NULL; - class_RemoteSyncAttribute = NULL; - class_MasterSyncAttribute = NULL; - class_PuppetSyncAttribute = NULL; - class_GodotMethodAttribute = NULL; - field_GodotMethodAttribute_methodName = NULL; - - field_GodotObject_ptr = NULL; - field_NodePath_ptr = NULL; - field_Image_ptr = NULL; - field_RID_ptr = NULL; + class_ExportAttribute = nullptr; + field_ExportAttribute_hint = nullptr; + field_ExportAttribute_hintString = nullptr; + class_SignalAttribute = nullptr; + class_ToolAttribute = nullptr; + class_RemoteAttribute = nullptr; + class_MasterAttribute = nullptr; + class_PuppetAttribute = nullptr; + class_RemoteSyncAttribute = nullptr; + class_MasterSyncAttribute = nullptr; + class_PuppetSyncAttribute = nullptr; + class_GodotMethodAttribute = nullptr; + field_GodotMethodAttribute_methodName = nullptr; + + field_GodotObject_ptr = nullptr; + field_StringName_ptr = nullptr; + field_NodePath_ptr = nullptr; + field_Image_ptr = nullptr; + field_RID_ptr = nullptr; methodthunk_GodotObject_Dispose.nullify(); methodthunk_Array_GetPtr.nullify(); methodthunk_Dictionary_GetPtr.nullify(); methodthunk_SignalAwaiter_SignalCallback.nullify(); - methodthunk_SignalAwaiter_FailureCallback.nullify(); methodthunk_GodotTaskScheduler_Activate.nullify(); + methodthunk_Delegate_Equals.nullify(); + + methodthunk_DelegateUtils_TrySerializeDelegate.nullify(); + methodthunk_DelegateUtils_TryDeserializeDelegate.nullify(); + // Start of MarshalUtils methods methodthunk_MarshalUtils_TypeIsGenericArray.nullify(); @@ -178,7 +189,7 @@ void CachedData::clear_godot_api_cache() { // End of MarshalUtils methods - task_scheduler_handle = Ref<MonoGCHandle>(); + task_scheduler_handle = Ref<MonoGCHandleRef>(); } #define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) @@ -211,6 +222,8 @@ void update_corlib_cache() { CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true)); #endif + CACHE_METHOD_THUNK_AND_CHECK(Delegate, Equals, GDMono::get_singleton()->get_corlib_assembly()->get_class("System", "Delegate")->get_method_with_desc("System.Delegate:Equals(object)", 1)); + CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException")); cached_data.corlib_cache_updated = true; @@ -219,23 +232,29 @@ void update_corlib_cache() { void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(Vector2, GODOT_API_CLASS(Vector2)); + CACHE_CLASS_AND_CHECK(Vector2i, GODOT_API_CLASS(Vector2i)); CACHE_CLASS_AND_CHECK(Rect2, GODOT_API_CLASS(Rect2)); + CACHE_CLASS_AND_CHECK(Rect2i, GODOT_API_CLASS(Rect2i)); CACHE_CLASS_AND_CHECK(Transform2D, GODOT_API_CLASS(Transform2D)); CACHE_CLASS_AND_CHECK(Vector3, GODOT_API_CLASS(Vector3)); + CACHE_CLASS_AND_CHECK(Vector3i, GODOT_API_CLASS(Vector3i)); CACHE_CLASS_AND_CHECK(Basis, GODOT_API_CLASS(Basis)); CACHE_CLASS_AND_CHECK(Quat, GODOT_API_CLASS(Quat)); CACHE_CLASS_AND_CHECK(Transform, GODOT_API_CLASS(Transform)); CACHE_CLASS_AND_CHECK(AABB, GODOT_API_CLASS(AABB)); CACHE_CLASS_AND_CHECK(Color, GODOT_API_CLASS(Color)); CACHE_CLASS_AND_CHECK(Plane, GODOT_API_CLASS(Plane)); + CACHE_CLASS_AND_CHECK(StringName, GODOT_API_CLASS(StringName)); CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath)); CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(RID)); CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object)); CACHE_CLASS_AND_CHECK(GodotResource, GODOT_API_CLASS(Resource)); CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node)); CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); - CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial)); + CACHE_CLASS_AND_CHECK(Node3D, GODOT_API_CLASS(Node3D)); CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef)); + CACHE_CLASS_AND_CHECK(Callable, GODOT_API_CLASS(Callable)); + CACHE_CLASS_AND_CHECK(SignalInfo, GODOT_API_CLASS(SignalInfo)); CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array)); CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)); CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); @@ -261,6 +280,7 @@ void update_godot_api_cache() { CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD)); + CACHE_FIELD_AND_CHECK(StringName, ptr, CACHED_CLASS(StringName)->get_field(BINDINGS_PTR_FIELD)); CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); @@ -268,9 +288,11 @@ void update_godot_api_cache() { CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method("GetPtr", 0)); CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method("GetPtr", 0)); CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, GODOT_API_CLASS(SignalAwaiter)->get_method("SignalCallback", 1)); - CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0)); CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)); + CACHE_METHOD_THUNK_AND_CHECK(DelegateUtils, TrySerializeDelegate, GODOT_API_CLASS(DelegateUtils)->get_method("TrySerializeDelegate", 2)); + CACHE_METHOD_THUNK_AND_CHECK(DelegateUtils, TryDeserializeDelegate, GODOT_API_CLASS(DelegateUtils)->get_method("TryDeserializeDelegate", 2)); + // Start of MarshalUtils methods CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericArray, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericArray", 1)); @@ -300,7 +322,7 @@ void update_godot_api_cache() { // TODO Move to CSharpLanguage::init() and do handle disposal MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler)); - cached_data.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); + cached_data.task_scheduler_handle = MonoGCHandleRef::create_strong(task_scheduler); cached_data.godot_api_cache_updated = true; } diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index 0458e91240..21c8ed4efe 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -73,23 +73,29 @@ struct CachedData { // ----------------------------------------------- GDMonoClass *class_Vector2; + GDMonoClass *class_Vector2i; GDMonoClass *class_Rect2; + GDMonoClass *class_Rect2i; GDMonoClass *class_Transform2D; GDMonoClass *class_Vector3; + GDMonoClass *class_Vector3i; GDMonoClass *class_Basis; GDMonoClass *class_Quat; GDMonoClass *class_Transform; GDMonoClass *class_AABB; GDMonoClass *class_Color; GDMonoClass *class_Plane; + GDMonoClass *class_StringName; GDMonoClass *class_NodePath; GDMonoClass *class_RID; GDMonoClass *class_GodotObject; GDMonoClass *class_GodotResource; GDMonoClass *class_Node; GDMonoClass *class_Control; - GDMonoClass *class_Spatial; + GDMonoClass *class_Node3D; GDMonoClass *class_WeakRef; + GDMonoClass *class_Callable; + GDMonoClass *class_SignalInfo; GDMonoClass *class_Array; GDMonoClass *class_Dictionary; GDMonoClass *class_MarshalUtils; @@ -115,6 +121,7 @@ struct CachedData { GDMonoField *field_GodotMethodAttribute_methodName; GDMonoField *field_GodotObject_ptr; + GDMonoField *field_StringName_ptr; GDMonoField *field_NodePath_ptr; GDMonoField *field_Image_ptr; GDMonoField *field_RID_ptr; @@ -123,9 +130,13 @@ struct CachedData { GDMonoMethodThunkR<Array *, MonoObject *> methodthunk_Array_GetPtr; GDMonoMethodThunkR<Dictionary *, MonoObject *> methodthunk_Dictionary_GetPtr; GDMonoMethodThunk<MonoObject *, MonoArray *> methodthunk_SignalAwaiter_SignalCallback; - GDMonoMethodThunk<MonoObject *> methodthunk_SignalAwaiter_FailureCallback; GDMonoMethodThunk<MonoObject *> methodthunk_GodotTaskScheduler_Activate; + GDMonoMethodThunkR<MonoBoolean, MonoObject *, MonoObject *> methodthunk_Delegate_Equals; + + GDMonoMethodThunkR<MonoBoolean, MonoDelegate *, MonoObject *> methodthunk_DelegateUtils_TrySerializeDelegate; + GDMonoMethodThunkR<MonoBoolean, MonoObject *, MonoDelegate **> methodthunk_DelegateUtils_TryDeserializeDelegate; + // Start of MarshalUtils methods GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericArray; @@ -148,7 +159,7 @@ struct CachedData { // End of MarshalUtils methods - Ref<MonoGCHandle> task_scheduler_handle; + Ref<MonoGCHandleRef> task_scheduler_handle; bool corlib_cache_updated; bool godot_api_cache_updated; @@ -193,10 +204,4 @@ _FORCE_INLINE_ bool tools_godot_api_check() { #define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoCache::cached_data.methodthunk_##m_class##_##m_method) #define CACHED_PROPERTY(m_class, m_property) (GDMonoCache::cached_data.property_##m_class##_##m_property) -#ifdef REAL_T_IS_DOUBLE -#define REAL_T_MONOCLASS CACHED_CLASS_RAW(double) -#else -#define REAL_T_MONOCLASS CACHED_CLASS_RAW(float) -#endif - #endif // GD_MONO_CACHE_H diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 648f5f6c6b..ede4203e35 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -40,7 +40,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) { // mono_type_get_full_name is not exposed to embedders, but this seems to do the job MonoReflectionType *type_obj = mono_type_get_object(mono_domain_get(), get_mono_type(p_mono_class)); - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc); UNHANDLED_EXCEPTION(exc); @@ -76,12 +76,12 @@ 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; + return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : nullptr; } 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; + return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : nullptr; } #ifdef TOOLS_ENABLED @@ -92,9 +92,9 @@ Vector<MonoClassField *> GDMonoClass::get_enum_fields() { Vector<MonoClassField *> enum_fields; - void *iter = NULL; - MonoClassField *raw_field = NULL; - while ((raw_field = mono_class_get_fields(get_mono_ptr(), &iter)) != NULL) { + void *iter = nullptr; + MonoClassField *raw_field = nullptr; + while ((raw_field = mono_class_get_fields(get_mono_ptr(), &iter)) != nullptr) { uint32_t field_flags = mono_field_get_flags(raw_field); // Enums have an instance field named value__ which holds the value of the enum. @@ -126,21 +126,21 @@ bool GDMonoClass::has_attribute(GDMonoClass *p_attr_class) { MonoObject *GDMonoClass::get_attribute(GDMonoClass *p_attr_class) { #ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, NULL); + ERR_FAIL_NULL_V(p_attr_class, nullptr); #endif if (!attrs_fetched) fetch_attributes(); if (!attributes) - return NULL; + return nullptr; return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); } void GDMonoClass::fetch_attributes() { - ERR_FAIL_COND(attributes != NULL); + ERR_FAIL_COND(attributes != nullptr); attributes = mono_custom_attrs_from_class(get_mono_ptr()); attrs_fetched = true; @@ -153,9 +153,9 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base if (methods_fetched) return; - void *iter = NULL; - MonoMethod *raw_method = NULL; - while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) { + void *iter = nullptr; + MonoMethod *raw_method = nullptr; + while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != nullptr) { StringName name = mono_method_get_name(raw_method); // get_method implicitly fetches methods and adds them to this->methods @@ -198,7 +198,7 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base } #endif - uint32_t flags = mono_method_get_flags(method->mono_method, NULL); + uint32_t flags = mono_method_get_flags(method->mono_method, nullptr); if (!(flags & MONO_METHOD_ATTR_VIRTUAL)) continue; @@ -242,21 +242,21 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base GDMonoMethod *GDMonoClass::get_fetched_method_unknown_params(const StringName &p_name) { - ERR_FAIL_COND_V(!methods_fetched, NULL); + ERR_FAIL_COND_V(!methods_fetched, nullptr); - const MethodKey *k = NULL; + const MethodKey *k = nullptr; while ((k = methods.next(k))) { if (k->name == p_name) return methods.get(*k); } - return NULL; + return nullptr; } bool GDMonoClass::has_fetched_method_unknown_params(const StringName &p_name) { - return get_fetched_method_unknown_params(p_name) != NULL; + return get_fetched_method_unknown_params(p_name) != nullptr; } bool GDMonoClass::implements_interface(GDMonoClass *p_interface) { @@ -274,7 +274,7 @@ GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_cou return *match; if (methods_fetched) - return NULL; + return nullptr; MonoMethod *raw_method = mono_class_get_method_from_name(mono_class, String(p_name).utf8().get_data(), p_params_count); @@ -285,7 +285,7 @@ GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_cou return method; } - return NULL; + return nullptr; } GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method) { @@ -307,7 +307,7 @@ GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count) { - ERR_FAIL_NULL_V(p_raw_method, NULL); + ERR_FAIL_NULL_V(p_raw_method, nullptr); MethodKey key = MethodKey(p_name, p_params_count); @@ -328,7 +328,7 @@ GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, boo MonoMethod *method = mono_method_desc_search_in_class(desc, mono_class); mono_method_desc_free(desc); - ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, NULL); + ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, nullptr); return get_method(method); } @@ -341,7 +341,7 @@ GDMonoField *GDMonoClass::get_field(const StringName &p_name) { return result->value(); if (fields_fetched) - return NULL; + return nullptr; MonoClassField *raw_field = mono_class_get_field_from_name(mono_class, String(p_name).utf8().get_data()); @@ -352,7 +352,7 @@ GDMonoField *GDMonoClass::get_field(const StringName &p_name) { return field; } - return NULL; + return nullptr; } const Vector<GDMonoField *> &GDMonoClass::get_all_fields() { @@ -360,9 +360,9 @@ const Vector<GDMonoField *> &GDMonoClass::get_all_fields() { if (fields_fetched) return fields_list; - void *iter = NULL; - MonoClassField *raw_field = NULL; - while ((raw_field = mono_class_get_fields(mono_class, &iter)) != NULL) { + void *iter = nullptr; + MonoClassField *raw_field = nullptr; + while ((raw_field = mono_class_get_fields(mono_class, &iter)) != nullptr) { StringName name = mono_field_get_name(raw_field); Map<StringName, GDMonoField *>::Element *match = fields.find(name); @@ -389,7 +389,7 @@ GDMonoProperty *GDMonoClass::get_property(const StringName &p_name) { return result->value(); if (properties_fetched) - return NULL; + return nullptr; MonoProperty *raw_property = mono_class_get_property_from_name(mono_class, String(p_name).utf8().get_data()); @@ -400,7 +400,7 @@ GDMonoProperty *GDMonoClass::get_property(const StringName &p_name) { return property; } - return NULL; + return nullptr; } const Vector<GDMonoProperty *> &GDMonoClass::get_all_properties() { @@ -408,9 +408,9 @@ const Vector<GDMonoProperty *> &GDMonoClass::get_all_properties() { if (properties_fetched) return properties_list; - void *iter = NULL; - MonoProperty *raw_property = NULL; - while ((raw_property = mono_class_get_properties(mono_class, &iter)) != NULL) { + void *iter = nullptr; + MonoProperty *raw_property = nullptr; + while ((raw_property = mono_class_get_properties(mono_class, &iter)) != nullptr) { StringName name = mono_property_get_name(raw_property); Map<StringName, GDMonoProperty *>::Element *match = properties.find(name); @@ -433,9 +433,9 @@ const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() { if (delegates_fetched) return delegates_list; - void *iter = NULL; - MonoClass *raw_class = NULL; - while ((raw_class = mono_class_get_nested_types(mono_class, &iter)) != NULL) { + void *iter = nullptr; + MonoClass *raw_class = nullptr; + while ((raw_class = mono_class_get_nested_types(mono_class, &iter)) != nullptr) { if (mono_class_is_delegate(raw_class)) { StringName name = mono_class_get_name(raw_class); @@ -459,9 +459,9 @@ const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() { const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() { if (!method_list_fetched) { - void *iter = NULL; - MonoMethod *raw_method = NULL; - while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) { + void *iter = nullptr; + MonoMethod *raw_method = nullptr; + while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != nullptr) { method_list.push_back(memnew(GDMonoMethod(mono_method_get_name(raw_method), raw_method))); } @@ -479,7 +479,7 @@ GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name assembly = p_assembly; attrs_fetched = false; - attributes = NULL; + attributes = nullptr; methods_fetched = false; method_list_fetched = false; @@ -512,7 +512,7 @@ GDMonoClass::~GDMonoClass() { Vector<GDMonoMethod *> deleted_methods; deleted_methods.resize(methods.size()); - const MethodKey *k = NULL; + const MethodKey *k = nullptr; while ((k = methods.next(k))) { GDMonoMethod *method = methods.get(*k); diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 03b56c9949..3f4e5fe5ac 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -37,6 +37,10 @@ #include "gd_mono_marshal.h" #include "gd_mono_utils.h" +void GDMonoField::set_value(MonoObject *p_object, MonoObject *p_value) { + mono_field_set_value(p_object, mono_field, p_value); +} + void GDMonoField::set_value_raw(MonoObject *p_object, void *p_ptr) { mono_field_set_value(p_object, mono_field, &p_ptr); } @@ -112,7 +116,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case MONO_TYPE_STRING: { if (p_value.get_type() == Variant::NIL) { // Otherwise, Variant -> String would return the string "Null" - MonoString *mono_string = NULL; + MonoString *mono_string = nullptr; mono_field_set_value(p_object, mono_field, mono_string); } else { MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value); @@ -128,11 +132,21 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } + if (tclass == CACHED_CLASS(Vector2i)) { + SET_FROM_STRUCT(Vector2i); + break; + } + if (tclass == CACHED_CLASS(Rect2)) { SET_FROM_STRUCT(Rect2); break; } + if (tclass == CACHED_CLASS(Rect2i)) { + SET_FROM_STRUCT(Rect2i); + break; + } + if (tclass == CACHED_CLASS(Transform2D)) { SET_FROM_STRUCT(Transform2D); break; @@ -143,6 +157,11 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } + if (tclass == CACHED_CLASS(Vector3i)) { + SET_FROM_STRUCT(Vector3i); + break; + } + if (tclass == CACHED_CLASS(Basis)) { SET_FROM_STRUCT(Basis); break; @@ -173,6 +192,18 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } + if (tclass == CACHED_CLASS(Callable)) { + GDMonoMarshal::M_Callable val = GDMonoMarshal::callable_to_managed(p_value.operator Callable()); + mono_field_set_value(p_object, mono_field, &val); + break; + } + + if (tclass == CACHED_CLASS(SignalInfo)) { + GDMonoMarshal::M_SignalInfo val = GDMonoMarshal::signal_info_to_managed(p_value.operator Signal()); + mono_field_set_value(p_object, mono_field, &val); + break; + } + if (mono_class_is_enum(tclass->get_mono_ptr())) { MonoType *enum_basetype = mono_class_enum_basetype(tclass->get_mono_ptr()); switch (mono_type_get_type(enum_basetype)) { @@ -256,11 +287,21 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } - if (array_type->eklass == REAL_T_MONOCLASS) { + if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) { + SET_FROM_ARRAY(PackedInt64Array); + break; + } + + if (array_type->eklass == CACHED_CLASS_RAW(float)) { SET_FROM_ARRAY(PackedFloat32Array); break; } + if (array_type->eklass == CACHED_CLASS_RAW(double)) { + SET_FROM_ARRAY(PackedFloat64Array); + break; + } + if (array_type->eklass == CACHED_CLASS_RAW(String)) { SET_FROM_ARRAY(PackedStringArray); break; @@ -294,6 +335,12 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } + if (CACHED_CLASS(StringName) == type_class) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator StringName()); + mono_field_set_value(p_object, mono_field, managed); + break; + } + if (CACHED_CLASS(NodePath) == type_class) { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath()); mono_field_set_value(p_object, mono_field, managed); @@ -386,12 +433,21 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case Variant::VECTOR2: { SET_FROM_STRUCT(Vector2); } break; + case Variant::VECTOR2I: { + SET_FROM_STRUCT(Vector2i); + } break; case Variant::RECT2: { SET_FROM_STRUCT(Rect2); } break; + case Variant::RECT2I: { + SET_FROM_STRUCT(Rect2i); + } break; case Variant::VECTOR3: { SET_FROM_STRUCT(Vector3); } break; + case Variant::VECTOR3I: { + SET_FROM_STRUCT(Vector3i); + } break; case Variant::TRANSFORM2D: { SET_FROM_STRUCT(Transform2D); } break; @@ -413,6 +469,10 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case Variant::COLOR: { SET_FROM_STRUCT(Color); } break; + case Variant::STRING_NAME: { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator StringName()); + mono_field_set_value(p_object, mono_field, managed); + } break; case Variant::NODE_PATH: { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath()); mono_field_set_value(p_object, mono_field, managed); @@ -424,8 +484,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case Variant::OBJECT: { MonoObject *managed = GDMonoUtils::unmanaged_get_managed(p_value.operator Object *()); mono_field_set_value(p_object, mono_field, managed); - break; - } + } break; + case Variant::CALLABLE: { + GDMonoMarshal::M_Callable val = GDMonoMarshal::callable_to_managed(p_value.operator Callable()); + mono_field_set_value(p_object, mono_field, &val); + } break; + case Variant::SIGNAL: { + GDMonoMarshal::M_SignalInfo val = GDMonoMarshal::signal_info_to_managed(p_value.operator Signal()); + mono_field_set_value(p_object, mono_field, &val); + } break; case Variant::DICTIONARY: { MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary)); mono_field_set_value(p_object, mono_field, managed); @@ -440,9 +507,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case Variant::PACKED_INT32_ARRAY: { SET_FROM_ARRAY(PackedInt32Array); } break; + case Variant::PACKED_INT64_ARRAY: { + SET_FROM_ARRAY(PackedInt64Array); + } break; case Variant::PACKED_FLOAT32_ARRAY: { SET_FROM_ARRAY(PackedFloat32Array); } break; + case Variant::PACKED_FLOAT64_ARRAY: { + SET_FROM_ARRAY(PackedFloat64Array); + } break; case Variant::PACKED_STRING_ARRAY: { SET_FROM_ARRAY(PackedStringArray); } break; @@ -550,19 +623,19 @@ bool GDMonoField::has_attribute(GDMonoClass *p_attr_class) { } MonoObject *GDMonoField::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, NULL); + ERR_FAIL_NULL_V(p_attr_class, nullptr); if (!attrs_fetched) fetch_attributes(); if (!attributes) - return NULL; + return nullptr; return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); } void GDMonoField::fetch_attributes() { - ERR_FAIL_COND(attributes != NULL); + ERR_FAIL_COND(attributes != nullptr); attributes = mono_custom_attrs_from_field(owner->get_mono_ptr(), mono_field); attrs_fetched = true; } @@ -598,7 +671,7 @@ GDMonoField::GDMonoField(MonoClassField *p_mono_field, GDMonoClass *p_owner) { type.type_class = GDMono::get_singleton()->get_class(field_type_class); attrs_fetched = false; - attributes = NULL; + attributes = nullptr; } GDMonoField::~GDMonoField() { diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h index 76ee0963c4..61f2c8f071 100644 --- a/modules/mono/mono_gd/gd_mono_field.h +++ b/modules/mono/mono_gd/gd_mono_field.h @@ -47,21 +47,22 @@ class GDMonoField : public IMonoClassMember { MonoCustomAttrInfo *attributes; public: - virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; } + virtual GDMonoClass *get_enclosing_class() const final { return owner; } - virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_FIELD; } + virtual MemberType get_member_type() const final { return MEMBER_TYPE_FIELD; } - virtual StringName get_name() const GD_FINAL { return name; } + virtual StringName get_name() const final { return name; } - virtual bool is_static() GD_FINAL; - virtual Visibility get_visibility() GD_FINAL; + virtual bool is_static() final; + virtual Visibility get_visibility() final; - virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL; + virtual bool has_attribute(GDMonoClass *p_attr_class) final; + virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final; void fetch_attributes(); _FORCE_INLINE_ ManagedType get_type() const { return type; } + void set_value(MonoObject *p_object, MonoObject *p_value); void set_value_raw(MonoObject *p_object, void *p_ptr); void set_value_from_variant(MonoObject *p_object, const Variant &p_value); diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index 74ffa90cb3..1898785699 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -33,11 +33,13 @@ #include "../csharp_script.h" #include "../mono_gc_handle.h" #include "../utils/macros.h" -#include "../utils/thread_local.h" #include "gd_mono_class.h" #include "gd_mono_marshal.h" #include "gd_mono_utils.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" + #include <mono/metadata/exception.h> namespace GDMonoInternals { @@ -59,7 +61,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { GDMonoClass *native = GDMonoUtils::get_class_native_base(klass); - CRASH_COND(native == NULL); + CRASH_COND(native == nullptr); if (native == klass) { // If it's just a wrapper Godot class and not a custom inheriting class, then attach a @@ -73,7 +75,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { script_binding.inited = true; script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass); script_binding.wrapper_class = klass; - script_binding.gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed); + script_binding.gchandle = ref ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed); script_binding.owner = unmanaged; if (ref) { @@ -99,15 +101,17 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { return; } - Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed); + MonoGCHandleData gchandle = ref ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed); Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass, native); CRASH_COND(script.is_null()); - ScriptInstance *si = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle); + CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle); + + unmanaged->set_script_and_instance(script, csharp_instance); - unmanaged->set_script_and_instance(script, si); + csharp_instance->connect_event_signals(); } void unhandled_exception(MonoException *p_exc) { @@ -115,13 +119,13 @@ void unhandled_exception(MonoException *p_exc) { if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) { // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders - GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL); + GDMono::unhandled_exception_hook((MonoObject *)p_exc, nullptr); GD_UNREACHABLE(); } else { #ifdef DEBUG_ENABLED GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); - if (ScriptDebugger::get_singleton()) - ScriptDebugger::get_singleton()->idle_poll(); + if (EngineDebugger::is_active()) + EngineDebugger::get_singleton()->poll_events(false); #endif } } diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index 76828a66e0..ca16c2b76a 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -46,13 +46,13 @@ static CharString get_default_log_level() { #endif } -GDMonoLog *GDMonoLog::singleton = NULL; +GDMonoLog *GDMonoLog::singleton = nullptr; -#if !defined(JAVASCRIPT_ENABLED) +#ifdef GD_MONO_LOG_ENABLED static int get_log_level_id(const char *p_log_level) { - const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", NULL }; + const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", nullptr }; int i = 0; while (valid_log_levels[i]) { @@ -191,7 +191,7 @@ GDMonoLog::GDMonoLog() { GDMonoLog::~GDMonoLog() { - singleton = NULL; + singleton = nullptr; if (log_file) { log_file->close(); @@ -213,7 +213,7 @@ GDMonoLog::GDMonoLog() { GDMonoLog::~GDMonoLog() { - singleton = NULL; + singleton = nullptr; } #endif // !defined(JAVASCRIPT_ENABLED) diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h index ecf4c78b1a..1fc21f7df5 100644 --- a/modules/mono/mono_gd/gd_mono_log.h +++ b/modules/mono/mono_gd/gd_mono_log.h @@ -35,13 +35,18 @@ #include "core/typedefs.h" -#if !defined(JAVASCRIPT_ENABLED) +#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED) +// We have custom mono log callbacks for WASM and iOS +#define GD_MONO_LOG_ENABLED +#endif + +#ifdef GD_MONO_LOG_ENABLED #include "core/os/file_access.h" #endif class GDMonoLog { -#if !defined(JAVASCRIPT_ENABLED) +#ifdef GD_MONO_LOG_ENABLED int log_level_id; FileAccess *log_file; diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 695be64d6e..1878038f44 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -30,13 +30,14 @@ #include "gd_mono_marshal.h" +#include "../signal_awaiter_utils.h" #include "gd_mono.h" #include "gd_mono_cache.h" #include "gd_mono_class.h" namespace GDMonoMarshal { -Variant::Type managed_to_variant_type(const ManagedType &p_type) { +Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_variant) { switch (p_type.type_encoding) { case MONO_TYPE_BOOLEAN: return Variant::BOOL; @@ -74,15 +75,24 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { if (vtclass == CACHED_CLASS(Vector2)) return Variant::VECTOR2; + if (vtclass == CACHED_CLASS(Vector2i)) + return Variant::VECTOR2I; + if (vtclass == CACHED_CLASS(Rect2)) return Variant::RECT2; + if (vtclass == CACHED_CLASS(Rect2i)) + return Variant::RECT2I; + if (vtclass == CACHED_CLASS(Transform2D)) return Variant::TRANSFORM2D; if (vtclass == CACHED_CLASS(Vector3)) return Variant::VECTOR3; + if (vtclass == CACHED_CLASS(Vector3i)) + return Variant::VECTOR3I; + if (vtclass == CACHED_CLASS(Basis)) return Variant::BASIS; @@ -101,6 +111,12 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { if (vtclass == CACHED_CLASS(Plane)) return Variant::PLANE; + if (vtclass == CACHED_CLASS(Callable)) + return Variant::CALLABLE; + + if (vtclass == CACHED_CLASS(SignalInfo)) + return Variant::SIGNAL; + if (mono_class_is_enum(vtclass->get_mono_ptr())) return Variant::INT; } break; @@ -118,9 +134,15 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) return Variant::PACKED_INT32_ARRAY; - if (array_type->eklass == REAL_T_MONOCLASS) + if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) + return Variant::PACKED_INT64_ARRAY; + + if (array_type->eklass == CACHED_CLASS_RAW(float)) return Variant::PACKED_FLOAT32_ARRAY; + if (array_type->eklass == CACHED_CLASS_RAW(double)) + return Variant::PACKED_FLOAT64_ARRAY; + if (array_type->eklass == CACHED_CLASS_RAW(String)) return Variant::PACKED_STRING_ARRAY; @@ -142,6 +164,10 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { return Variant::OBJECT; } + if (CACHED_CLASS(StringName) == type_class) { + return Variant::STRING_NAME; + } + if (CACHED_CLASS(NodePath) == type_class) { return Variant::NODE_PATH; } @@ -179,6 +205,12 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { } } break; + case MONO_TYPE_OBJECT: { + if (r_nil_is_variant) + *r_nil_is_variant = true; + return Variant::NIL; + } break; + case MONO_TYPE_GENERICINST: { MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); @@ -211,6 +243,9 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { } break; } + if (r_nil_is_variant) + *r_nil_is_variant = false; + // Unknown return Variant::NIL; } @@ -375,7 +410,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty case MONO_TYPE_STRING: { if (p_var->get_type() == Variant::NIL) - return NULL; // Otherwise, Variant -> String would return the string "Null" + return nullptr; // Otherwise, Variant -> String would return the string "Null" return (MonoObject *)mono_string_from_godot(p_var->operator String()); } break; @@ -387,11 +422,21 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from); } + if (vtclass == CACHED_CLASS(Vector2i)) { + GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_var->operator ::Vector2i()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2i), &from); + } + if (vtclass == CACHED_CLASS(Rect2)) { GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from); } + if (vtclass == CACHED_CLASS(Rect2i)) { + GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_var->operator ::Rect2i()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2i), &from); + } + if (vtclass == CACHED_CLASS(Transform2D)) { GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from); @@ -402,6 +447,11 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from); } + if (vtclass == CACHED_CLASS(Vector3i)) { + GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_var->operator ::Vector3i()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3i), &from); + } + if (vtclass == CACHED_CLASS(Basis)) { GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var->operator ::Basis()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from); @@ -432,6 +482,16 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from); } + if (vtclass == CACHED_CLASS(Callable)) { + GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var->operator Callable()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Callable), &from); + } + + if (vtclass == CACHED_CLASS(SignalInfo)) { + GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var->operator Signal()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(SignalInfo), &from); + } + if (mono_class_is_enum(vtclass->get_mono_ptr())) { MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr()); MonoClass *enum_baseclass = mono_class_from_mono_type(enum_basetype); @@ -477,7 +537,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return BOX_ENUM(enum_baseclass, val); } default: { - ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed enum value of unmarshallable base type."); + ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to a managed enum value of unmarshallable base type."); } } } @@ -496,9 +556,15 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) return (MonoObject *)PackedInt32Array_to_mono_array(p_var->operator PackedInt32Array()); - if (array_type->eklass == REAL_T_MONOCLASS) + if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) + return (MonoObject *)PackedInt64Array_to_mono_array(p_var->operator PackedInt64Array()); + + if (array_type->eklass == CACHED_CLASS_RAW(float)) return (MonoObject *)PackedFloat32Array_to_mono_array(p_var->operator PackedFloat32Array()); + if (array_type->eklass == CACHED_CLASS_RAW(double)) + return (MonoObject *)PackedFloat64Array_to_mono_array(p_var->operator PackedFloat64Array()); + if (array_type->eklass == CACHED_CLASS_RAW(String)) return (MonoObject *)PackedStringArray_to_mono_array(p_var->operator PackedStringArray()); @@ -511,7 +577,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty if (array_type->eklass == CACHED_CLASS_RAW(Color)) return (MonoObject *)PackedColorArray_to_mono_array(p_var->operator PackedColorArray()); - ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed array of unmarshallable element type."); + ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to a managed array of unmarshallable element type."); } break; case MONO_TYPE_CLASS: { @@ -522,6 +588,10 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *()); } + if (CACHED_CLASS(StringName) == type_class) { + return GDMonoUtils::create_managed_from(p_var->operator StringName()); + } + if (CACHED_CLASS(NodePath) == type_class) { return GDMonoUtils::create_managed_from(p_var->operator NodePath()); } @@ -592,14 +662,26 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var->operator ::Vector2()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from); } + case Variant::VECTOR2I: { + GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_var->operator ::Vector2i()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2i), &from); + } case Variant::RECT2: { GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from); } + case Variant::RECT2I: { + GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_var->operator ::Rect2i()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2i), &from); + } case Variant::VECTOR3: { GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var->operator ::Vector3()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from); } + case Variant::VECTOR3I: { + GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_var->operator ::Vector3i()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3i), &from); + } case Variant::TRANSFORM2D: { GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from); @@ -628,12 +710,22 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var->operator ::Color()); return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from); } + case Variant::STRING_NAME: + return GDMonoUtils::create_managed_from(p_var->operator StringName()); case Variant::NODE_PATH: return GDMonoUtils::create_managed_from(p_var->operator NodePath()); case Variant::_RID: return GDMonoUtils::create_managed_from(p_var->operator RID()); case Variant::OBJECT: return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *()); + case Variant::CALLABLE: { + GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var->operator Callable()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Callable), &from); + } + case Variant::SIGNAL: { + GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var->operator Signal()); + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(SignalInfo), &from); + } case Variant::DICTIONARY: return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary)); case Variant::ARRAY: @@ -642,8 +734,12 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return (MonoObject *)PackedByteArray_to_mono_array(p_var->operator PackedByteArray()); case Variant::PACKED_INT32_ARRAY: return (MonoObject *)PackedInt32Array_to_mono_array(p_var->operator PackedInt32Array()); + case Variant::PACKED_INT64_ARRAY: + return (MonoObject *)PackedInt64Array_to_mono_array(p_var->operator PackedInt64Array()); case Variant::PACKED_FLOAT32_ARRAY: return (MonoObject *)PackedFloat32Array_to_mono_array(p_var->operator PackedFloat32Array()); + case Variant::PACKED_FLOAT64_ARRAY: + return (MonoObject *)PackedFloat64Array_to_mono_array(p_var->operator PackedFloat64Array()); case Variant::PACKED_STRING_ARRAY: return (MonoObject *)PackedStringArray_to_mono_array(p_var->operator PackedStringArray()); case Variant::PACKED_VECTOR2_ARRAY: @@ -653,7 +749,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty case Variant::PACKED_COLOR_ARRAY: return (MonoObject *)PackedColorArray_to_mono_array(p_var->operator PackedColorArray()); default: - return NULL; + return nullptr; } break; case MONO_TYPE_GENERICINST: { @@ -696,8 +792,8 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } break; } - ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to an unmarshallable managed type. Name: '" + - p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + "."); + ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to an unmarshallable managed type. Name: '" + + p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + "."); } Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type, bool p_fail_with_err = true) { @@ -735,7 +831,7 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type return unbox<double>(p_obj); case MONO_TYPE_STRING: { - if (p_obj == NULL) + if (p_obj == nullptr) return Variant(); // NIL return mono_string_to_godot_not_null((MonoString *)p_obj); } break; @@ -744,34 +840,49 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type GDMonoClass *vtclass = p_type.type_class; if (vtclass == CACHED_CLASS(Vector2)) - return MARSHALLED_IN(Vector2, (GDMonoMarshal::M_Vector2 *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Vector2, unbox_addr<GDMonoMarshal::M_Vector2>(p_obj)); + + if (vtclass == CACHED_CLASS(Vector2i)) + return MARSHALLED_IN(Vector2i, unbox_addr<GDMonoMarshal::M_Vector2i>(p_obj)); if (vtclass == CACHED_CLASS(Rect2)) - return MARSHALLED_IN(Rect2, (GDMonoMarshal::M_Rect2 *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Rect2, unbox_addr<GDMonoMarshal::M_Rect2>(p_obj)); + + if (vtclass == CACHED_CLASS(Rect2i)) + return MARSHALLED_IN(Rect2i, unbox_addr<GDMonoMarshal::M_Rect2i>(p_obj)); if (vtclass == CACHED_CLASS(Transform2D)) - return MARSHALLED_IN(Transform2D, (GDMonoMarshal::M_Transform2D *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Transform2D, unbox_addr<GDMonoMarshal::M_Transform2D>(p_obj)); if (vtclass == CACHED_CLASS(Vector3)) - return MARSHALLED_IN(Vector3, (GDMonoMarshal::M_Vector3 *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Vector3, unbox_addr<GDMonoMarshal::M_Vector3>(p_obj)); + + if (vtclass == CACHED_CLASS(Vector3i)) + return MARSHALLED_IN(Vector3i, unbox_addr<GDMonoMarshal::M_Vector3i>(p_obj)); if (vtclass == CACHED_CLASS(Basis)) - return MARSHALLED_IN(Basis, (GDMonoMarshal::M_Basis *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Basis, unbox_addr<GDMonoMarshal::M_Basis>(p_obj)); if (vtclass == CACHED_CLASS(Quat)) - return MARSHALLED_IN(Quat, (GDMonoMarshal::M_Quat *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Quat, unbox_addr<GDMonoMarshal::M_Quat>(p_obj)); if (vtclass == CACHED_CLASS(Transform)) - return MARSHALLED_IN(Transform, (GDMonoMarshal::M_Transform *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Transform, unbox_addr<GDMonoMarshal::M_Transform>(p_obj)); if (vtclass == CACHED_CLASS(AABB)) - return MARSHALLED_IN(AABB, (GDMonoMarshal::M_AABB *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(AABB, unbox_addr<GDMonoMarshal::M_AABB>(p_obj)); if (vtclass == CACHED_CLASS(Color)) - return MARSHALLED_IN(Color, (GDMonoMarshal::M_Color *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Color, unbox_addr<GDMonoMarshal::M_Color>(p_obj)); if (vtclass == CACHED_CLASS(Plane)) - return MARSHALLED_IN(Plane, (GDMonoMarshal::M_Plane *)mono_object_unbox(p_obj)); + return MARSHALLED_IN(Plane, unbox_addr<GDMonoMarshal::M_Plane>(p_obj)); + + if (vtclass == CACHED_CLASS(Callable)) + return managed_to_callable(unbox<GDMonoMarshal::M_Callable>(p_obj)); + + if (vtclass == CACHED_CLASS(SignalInfo)) + return managed_to_signal_info(unbox<GDMonoMarshal::M_SignalInfo>(p_obj)); if (mono_class_is_enum(vtclass->get_mono_ptr())) return unbox<int32_t>(p_obj); @@ -790,9 +901,15 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) return mono_array_to_PackedInt32Array((MonoArray *)p_obj); - if (array_type->eklass == REAL_T_MONOCLASS) + if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) + return mono_array_to_PackedInt64Array((MonoArray *)p_obj); + + if (array_type->eklass == CACHED_CLASS_RAW(float)) return mono_array_to_PackedFloat32Array((MonoArray *)p_obj); + if (array_type->eklass == CACHED_CLASS_RAW(double)) + return mono_array_to_PackedFloat64Array((MonoArray *)p_obj); + if (array_type->eklass == CACHED_CLASS_RAW(String)) return mono_array_to_PackedStringArray((MonoArray *)p_obj); @@ -818,13 +935,18 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type // GodotObject if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { Object *ptr = unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_obj)); - if (ptr != NULL) { + if (ptr != nullptr) { Reference *ref = Object::cast_to<Reference>(ptr); return ref ? Variant(Ref<Reference>(ref)) : Variant(ptr); } return Variant(); } + if (CACHED_CLASS(StringName) == type_class) { + StringName *ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_obj)); + return ptr ? Variant(*ptr) : Variant(); + } + if (CACHED_CLASS(NodePath) == type_class) { NodePath *ptr = unbox<NodePath *>(CACHED_FIELD(NodePath, ptr)->get_value(p_obj)); return ptr ? Variant(*ptr) : Variant(); @@ -836,14 +958,14 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type } if (CACHED_CLASS(Array) == type_class) { - MonoException *exc = NULL; + MonoException *exc = nullptr; Array *ptr = CACHED_METHOD_THUNK(Array, GetPtr).invoke(p_obj, &exc); UNHANDLED_EXCEPTION(exc); return ptr ? Variant(*ptr) : Variant(); } if (CACHED_CLASS(Dictionary) == type_class) { - MonoException *exc = NULL; + MonoException *exc = nullptr; Dictionary *ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr).invoke(p_obj, &exc); UNHANDLED_EXCEPTION(exc); return ptr ? Variant(*ptr) : Variant(); @@ -874,14 +996,14 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); UNHANDLED_EXCEPTION(exc); return *unbox<Dictionary *>(ret); } if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); UNHANDLED_EXCEPTION(exc); return *unbox<Array *>(ret); @@ -942,9 +1064,9 @@ String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc) { ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj)); Variant var = GDMonoMarshal::mono_object_to_variant_no_err(p_obj, type); - if (var.get_type() == Variant::NIL && p_obj != NULL) { + if (var.get_type() == Variant::NIL && p_obj != nullptr) { // Cannot convert MonoObject* to Variant; fallback to 'ToString()'. - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoString *mono_str = GDMonoUtils::object_to_string(p_obj, &exc); if (exc) { @@ -960,9 +1082,10 @@ String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc) { } MonoArray *Array_to_mono_array(const Array &p_array) { - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_array.size()); + int length = p_array.size(); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), length); - for (int i = 0; i < p_array.size(); i++) { + for (int i = 0; i < length; i++) { MonoObject *boxed = variant_to_mono_object(p_array[i]); mono_array_setref(ret, i, boxed); } @@ -985,16 +1108,14 @@ Array mono_array_to_Array(MonoArray *p_array) { return ret; } -// TODO: Use memcpy where possible - MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array) { - const int *r = p_array.ptr(); + const int32_t *src = p_array.ptr(); + int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), p_array.size()); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), length); - for (int i = 0; i < p_array.size(); i++) { - mono_array_set(ret, int32_t, i, r[i]); - } + int32_t *dst = (int32_t *)mono_array_addr(ret, int32_t, 0); + memcpy(dst, src, length); return ret; } @@ -1005,23 +1126,48 @@ PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); - int *w = ret.ptrw(); + int32_t *dst = ret.ptrw(); - for (int i = 0; i < length; i++) { - w[i] = mono_array_get(p_array, int32_t, i); - } + const int32_t *src = (const int32_t *)mono_array_addr(p_array, int32_t, 0); + memcpy(dst, src, length); + + return ret; +} + +MonoArray *PackedInt64Array_to_mono_array(const PackedInt64Array &p_array) { + const int64_t *src = p_array.ptr(); + int length = p_array.size(); + + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int64_t), length); + + int64_t *dst = (int64_t *)mono_array_addr(ret, int64_t, 0); + memcpy(dst, src, length); + + return ret; +} + +PackedInt64Array mono_array_to_PackedInt64Array(MonoArray *p_array) { + PackedInt64Array ret; + if (!p_array) + return ret; + int length = mono_array_length(p_array); + ret.resize(length); + int64_t *dst = ret.ptrw(); + + const int64_t *src = (const int64_t *)mono_array_addr(p_array, int64_t, 0); + memcpy(dst, src, length); return ret; } MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array) { - const uint8_t *r = p_array.ptr(); + const uint8_t *src = p_array.ptr(); + int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), p_array.size()); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), length); - for (int i = 0; i < p_array.size(); i++) { - mono_array_set(ret, uint8_t, i, r[i]); - } + uint8_t *dst = (uint8_t *)mono_array_addr(ret, uint8_t, 0); + memcpy(dst, src, length); return ret; } @@ -1032,23 +1178,22 @@ PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); - uint8_t *w = ret.ptrw(); + uint8_t *dst = ret.ptrw(); - for (int i = 0; i < length; i++) { - w[i] = mono_array_get(p_array, uint8_t, i); - } + const uint8_t *src = (const uint8_t *)mono_array_addr(p_array, uint8_t, 0); + memcpy(dst, src, length); return ret; } MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array) { - const real_t *r = p_array.ptr(); + const float *src = p_array.ptr(); + int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), REAL_T_MONOCLASS, p_array.size()); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(float), length); - for (int i = 0; i < p_array.size(); i++) { - mono_array_set(ret, real_t, i, r[i]); - } + float *dst = (float *)mono_array_addr(ret, float, 0); + memcpy(dst, src, length); return ret; } @@ -1059,21 +1204,47 @@ PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); - real_t *w = ret.ptrw(); + float *dst = ret.ptrw(); - for (int i = 0; i < length; i++) { - w[i] = mono_array_get(p_array, real_t, i); - } + const float *src = (const float *)mono_array_addr(p_array, float, 0); + memcpy(dst, src, length); + + return ret; +} + +MonoArray *PackedFloat64Array_to_mono_array(const PackedFloat64Array &p_array) { + const double *src = p_array.ptr(); + int length = p_array.size(); + + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(double), length); + + double *dst = (double *)mono_array_addr(ret, double, 0); + memcpy(dst, src, length); + + return ret; +} + +PackedFloat64Array mono_array_to_PackedFloat64Array(MonoArray *p_array) { + PackedFloat64Array ret; + if (!p_array) + return ret; + int length = mono_array_length(p_array); + ret.resize(length); + double *dst = ret.ptrw(); + + const double *src = (const double *)mono_array_addr(p_array, double, 0); + memcpy(dst, src, length); return ret; } MonoArray *PackedStringArray_to_mono_array(const PackedStringArray &p_array) { const String *r = p_array.ptr(); + int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_array.size()); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), length); - for (int i = 0; i < p_array.size(); i++) { + for (int i = 0; i < length; i++) { MonoString *boxed = mono_string_from_godot(r[i]); mono_array_setref(ret, i, boxed); } @@ -1098,13 +1269,19 @@ PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array) { } MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array) { - const Color *r = p_array.ptr(); + const Color *src = p_array.ptr(); + int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), p_array.size()); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), length); - for (int i = 0; i < p_array.size(); i++) { - M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i); - *raw = MARSHALLED_OUT(Color, r[i]); + if constexpr (InteropLayout::MATCHES_Color) { + Color *dst = (Color *)mono_array_addr(ret, Color, 0); + memcpy(dst, src, length); + } else { + for (int i = 0; i < length; i++) { + M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i); + *raw = MARSHALLED_OUT(Color, src[i]); + } } return ret; @@ -1116,23 +1293,34 @@ PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); - Color *w = ret.ptrw(); + Color *dst = ret.ptrw(); - for (int i = 0; i < length; i++) { - w[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i)); + if constexpr (InteropLayout::MATCHES_Color) { + const Color *src = (const Color *)mono_array_addr(p_array, Color, 0); + memcpy(dst, src, length); + } else { + for (int i = 0; i < length; i++) { + dst[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i)); + } } return ret; } MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array) { - const Vector2 *r = p_array.ptr(); + const Vector2 *src = p_array.ptr(); + int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), p_array.size()); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), length); - for (int i = 0; i < p_array.size(); i++) { - M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i); - *raw = MARSHALLED_OUT(Vector2, r[i]); + if constexpr (InteropLayout::MATCHES_Vector2) { + Vector2 *dst = (Vector2 *)mono_array_addr(ret, Vector2, 0); + memcpy(dst, src, length); + } else { + for (int i = 0; i < length; i++) { + M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i); + *raw = MARSHALLED_OUT(Vector2, src[i]); + } } return ret; @@ -1144,23 +1332,34 @@ PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); - Vector2 *w = ret.ptrw(); + Vector2 *dst = ret.ptrw(); - for (int i = 0; i < length; i++) { - w[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i)); + if constexpr (InteropLayout::MATCHES_Vector2) { + const Vector2 *src = (const Vector2 *)mono_array_addr(p_array, Vector2, 0); + memcpy(dst, src, length); + } else { + for (int i = 0; i < length; i++) { + dst[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i)); + } } return ret; } MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array) { - const Vector3 *r = p_array.ptr(); + const Vector3 *src = p_array.ptr(); + int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), p_array.size()); + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), length); - for (int i = 0; i < p_array.size(); i++) { - M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i); - *raw = MARSHALLED_OUT(Vector3, r[i]); + if constexpr (InteropLayout::MATCHES_Vector3) { + Vector3 *dst = (Vector3 *)mono_array_addr(ret, Vector3, 0); + memcpy(dst, src, length); + } else { + for (int i = 0; i < length; i++) { + M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i); + *raw = MARSHALLED_OUT(Vector3, src[i]); + } } return ret; @@ -1172,13 +1371,85 @@ PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array) { return ret; int length = mono_array_length(p_array); ret.resize(length); - Vector3 *w = ret.ptrw(); + Vector3 *dst = ret.ptrw(); - for (int i = 0; i < length; i++) { - w[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i)); + if constexpr (InteropLayout::MATCHES_Vector3) { + const Vector3 *src = (const Vector3 *)mono_array_addr(p_array, Vector3, 0); + memcpy(dst, src, length); + } else { + for (int i = 0; i < length; i++) { + dst[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i)); + } } return ret; } +Callable managed_to_callable(const M_Callable &p_managed_callable) { + if (p_managed_callable.delegate) { + // TODO: Use pooling for ManagedCallable instances. + CallableCustom *managed_callable = memnew(ManagedCallable(p_managed_callable.delegate)); + return Callable(managed_callable); + } else { + Object *target = p_managed_callable.target ? + unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_callable.target)) : + nullptr; + StringName *method_ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_callable.method_string_name)); + StringName method = method_ptr ? *method_ptr : StringName(); + return Callable(target, method); + } +} + +M_Callable callable_to_managed(const Callable &p_callable) { + if (p_callable.is_custom()) { + CallableCustom *custom = p_callable.get_custom(); + CallableCustom::CompareEqualFunc compare_equal_func = custom->get_compare_equal_func(); + + if (compare_equal_func == ManagedCallable::compare_equal_func_ptr) { + ManagedCallable *managed_callable = static_cast<ManagedCallable *>(custom); + return { + nullptr, nullptr, + managed_callable->get_delegate() + }; + } else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) { + SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom); + return { + GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(signal_awaiter_callable->get_object())), + GDMonoUtils::create_managed_from(signal_awaiter_callable->get_signal()), + nullptr + }; + } else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) { + EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom); + return { + GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(event_signal_callable->get_object())), + GDMonoUtils::create_managed_from(event_signal_callable->get_signal()), + nullptr + }; + } + + // Some other CallableCustom. We only support ManagedCallable. + return { nullptr, nullptr, nullptr }; + } else { + MonoObject *target_managed = GDMonoUtils::unmanaged_get_managed(p_callable.get_object()); + MonoObject *method_string_name_managed = GDMonoUtils::create_managed_from(p_callable.get_method()); + return { target_managed, method_string_name_managed, nullptr }; + } +} + +Signal managed_to_signal_info(const M_SignalInfo &p_managed_signal) { + Object *owner = p_managed_signal.owner ? + unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_signal.owner)) : + nullptr; + StringName *name_ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_signal.name_string_name)); + StringName name = name_ptr ? *name_ptr : StringName(); + return Signal(owner, name); +} + +M_SignalInfo signal_info_to_managed(const Signal &p_signal) { + Object *owner = p_signal.get_object(); + MonoObject *owner_managed = GDMonoUtils::unmanaged_get_managed(owner); + MonoObject *name_string_name_managed = GDMonoUtils::create_managed_from(p_signal.get_name()); + return { owner_managed, name_string_name_managed }; +} + } // namespace GDMonoMarshal diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h index 5db59522ce..8b405291a7 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ b/modules/mono/mono_gd/gd_mono_marshal.h @@ -33,6 +33,7 @@ #include "core/variant.h" +#include "../managed_callable.h" #include "gd_mono.h" #include "gd_mono_utils.h" @@ -62,7 +63,7 @@ T *unbox_addr(MonoObject *p_obj) { #define BOX_PTR(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(IntPtr), x) #define BOX_ENUM(m_enum_class, x) mono_value_box(mono_domain_get(), m_enum_class, &x) -Variant::Type managed_to_variant_type(const ManagedType &p_type); +Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_variant = nullptr); bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type); bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type); @@ -80,7 +81,7 @@ _FORCE_INLINE_ String mono_string_to_godot_not_null(MonoString *p_mono_string) { } _FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) { - if (p_mono_string == NULL) + if (p_mono_string == nullptr) return String(); return mono_string_to_godot_not_null(p_mono_string); @@ -132,6 +133,11 @@ Array mono_array_to_Array(MonoArray *p_array); MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array); PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array); +// PackedInt64Array + +MonoArray *PackedInt64Array_to_mono_array(const PackedInt64Array &p_array); +PackedInt64Array mono_array_to_PackedInt64Array(MonoArray *p_array); + // PackedByteArray MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array); @@ -142,6 +148,11 @@ PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array); MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array); PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array); +// PackedFloat64Array + +MonoArray *PackedFloat64Array_to_mono_array(const PackedFloat64Array &p_array); +PackedFloat64Array mono_array_to_PackedFloat64Array(MonoArray *p_array); + // PackedStringArray MonoArray *PackedStringArray_to_mono_array(const PackedStringArray &p_array); @@ -162,11 +173,36 @@ PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array); MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array); PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array); +#pragma pack(push, 1) + +struct M_Callable { + MonoObject *target; + MonoObject *method_string_name; + MonoDelegate *delegate; +}; + +struct M_SignalInfo { + MonoObject *owner; + MonoObject *name_string_name; +}; + +#pragma pack(pop) + +// Callable +Callable managed_to_callable(const M_Callable &p_managed_callable); +M_Callable callable_to_managed(const Callable &p_callable); + +// SignalInfo +Signal managed_to_signal_info(const M_SignalInfo &p_managed_signal); +M_SignalInfo signal_info_to_managed(const Signal &p_signal); + // Structures namespace InteropLayout { enum { + MATCHES_int = (sizeof(int32_t) == sizeof(uint32_t)), + MATCHES_float = (sizeof(float) == sizeof(uint32_t)), MATCHES_double = (sizeof(double) == sizeof(uint64_t)), @@ -181,10 +217,18 @@ enum { offsetof(Vector2, x) == (sizeof(real_t) * 0) && offsetof(Vector2, y) == (sizeof(real_t) * 1)), + MATCHES_Vector2i = (MATCHES_int && (sizeof(Vector2i) == (sizeof(int32_t) * 2)) && + offsetof(Vector2i, x) == (sizeof(int32_t) * 0) && + offsetof(Vector2i, y) == (sizeof(int32_t) * 1)), + MATCHES_Rect2 = (MATCHES_Vector2 && (sizeof(Rect2) == (sizeof(Vector2) * 2)) && offsetof(Rect2, position) == (sizeof(Vector2) * 0) && offsetof(Rect2, size) == (sizeof(Vector2) * 1)), + MATCHES_Rect2i = (MATCHES_Vector2i && (sizeof(Rect2i) == (sizeof(Vector2i) * 2)) && + offsetof(Rect2i, position) == (sizeof(Vector2i) * 0) && + offsetof(Rect2i, size) == (sizeof(Vector2i) * 1)), + MATCHES_Transform2D = (MATCHES_Vector2 && (sizeof(Transform2D) == (sizeof(Vector2) * 3))), // No field offset required, it stores an array MATCHES_Vector3 = (MATCHES_real_t && (sizeof(Vector3) == (sizeof(real_t) * 3)) && @@ -192,6 +236,11 @@ enum { offsetof(Vector3, y) == (sizeof(real_t) * 1) && offsetof(Vector3, z) == (sizeof(real_t) * 2)), + MATCHES_Vector3i = (MATCHES_int && (sizeof(Vector3i) == (sizeof(int32_t) * 3)) && + offsetof(Vector3i, x) == (sizeof(int32_t) * 0) && + offsetof(Vector3i, y) == (sizeof(int32_t) * 1) && + offsetof(Vector3i, z) == (sizeof(int32_t) * 2)), + MATCHES_Basis = (MATCHES_Vector3 && (sizeof(Basis) == (sizeof(Vector3) * 3))), // No field offset required, it stores an array MATCHES_Quat = (MATCHES_real_t && (sizeof(Quat) == (sizeof(real_t) * 4)) && @@ -222,8 +271,9 @@ enum { // In the future we may force this if we want to ref return these structs #ifdef GD_MONO_FORCE_INTEROP_STRUCT_COPY /* clang-format off */ -GD_STATIC_ASSERT(MATCHES_Vector2 && MATCHES_Rect2 && MATCHES_Transform2D && MATCHES_Vector3 && - MATCHES_Basis && MATCHES_Quat && MATCHES_Transform && MATCHES_AABB && MATCHES_Color &&MATCHES_Plane); +static_assert(MATCHES_Vector2 && MATCHES_Rect2 && MATCHES_Transform2D && MATCHES_Vector3 && + MATCHES_Basis && MATCHES_Quat && MATCHES_Transform && MATCHES_AABB && MATCHES_Color && + MATCHES_Plane && MATCHES_Vector2i && MATCHES_Rect2i && MATCHES_Vector3i); /* clang-format on */ #endif @@ -244,6 +294,19 @@ struct M_Vector2 { } }; +struct M_Vector2i { + int32_t x, y; + + static _FORCE_INLINE_ Vector2i convert_to(const M_Vector2i &p_from) { + return Vector2i(p_from.x, p_from.y); + } + + static _FORCE_INLINE_ M_Vector2i convert_from(const Vector2i &p_from) { + M_Vector2i ret = { p_from.x, p_from.y }; + return ret; + } +}; + struct M_Rect2 { M_Vector2 position; M_Vector2 size; @@ -259,6 +322,21 @@ struct M_Rect2 { } }; +struct M_Rect2i { + M_Vector2i position; + M_Vector2i size; + + static _FORCE_INLINE_ Rect2i convert_to(const M_Rect2i &p_from) { + return Rect2i(M_Vector2i::convert_to(p_from.position), + M_Vector2i::convert_to(p_from.size)); + } + + static _FORCE_INLINE_ M_Rect2i convert_from(const Rect2i &p_from) { + M_Rect2i ret = { M_Vector2i::convert_from(p_from.position), M_Vector2i::convert_from(p_from.size) }; + return ret; + } +}; + struct M_Transform2D { M_Vector2 elements[3]; @@ -291,6 +369,19 @@ struct M_Vector3 { } }; +struct M_Vector3i { + int32_t x, y, z; + + static _FORCE_INLINE_ Vector3i convert_to(const M_Vector3i &p_from) { + return Vector3i(p_from.x, p_from.y, p_from.z); + } + + static _FORCE_INLINE_ M_Vector3i convert_from(const Vector3i &p_from) { + M_Vector3i ret = { p_from.x, p_from.y, p_from.z }; + return ret; + } +}; + struct M_Basis { M_Vector3 elements[3]; @@ -416,9 +507,12 @@ struct M_Plane { } DECL_TYPE_MARSHAL_TEMPLATES(Vector2) +DECL_TYPE_MARSHAL_TEMPLATES(Vector2i) DECL_TYPE_MARSHAL_TEMPLATES(Rect2) +DECL_TYPE_MARSHAL_TEMPLATES(Rect2i) DECL_TYPE_MARSHAL_TEMPLATES(Transform2D) DECL_TYPE_MARSHAL_TEMPLATES(Vector3) +DECL_TYPE_MARSHAL_TEMPLATES(Vector3i) DECL_TYPE_MARSHAL_TEMPLATES(Basis) DECL_TYPE_MARSHAL_TEMPLATES(Quat) DECL_TYPE_MARSHAL_TEMPLATES(Transform) diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp index 971c5ac737..c8cc5247c6 100644 --- a/modules/mono/mono_gd/gd_mono_method.cpp +++ b/modules/mono/mono_gd/gd_mono_method.cpp @@ -58,9 +58,9 @@ void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) { } } - void *iter = NULL; + void *iter = nullptr; MonoType *param_raw_type; - while ((param_raw_type = mono_signature_get_params(p_method_sig, &iter)) != NULL) { + while ((param_raw_type = mono_signature_get_params(p_method_sig, &iter)) != nullptr) { ManagedType param_type; param_type.type_encoding = mono_type_get_type(param_raw_type); @@ -81,11 +81,11 @@ GDMonoClass *GDMonoMethod::get_enclosing_class() const { } bool GDMonoMethod::is_static() { - return mono_method_get_flags(mono_method, NULL) & MONO_METHOD_ATTR_STATIC; + return mono_method_get_flags(mono_method, nullptr) & MONO_METHOD_ATTR_STATIC; } IMonoClassMember::Visibility GDMonoMethod::get_visibility() { - switch (mono_method_get_flags(mono_method, NULL) & MONO_METHOD_ATTR_ACCESS_MASK) { + switch (mono_method_get_flags(mono_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) { case MONO_METHOD_ATTR_PRIVATE: return IMonoClassMember::PRIVATE; case MONO_METHOD_ATTR_FAM_AND_ASSEM: @@ -101,55 +101,46 @@ IMonoClassMember::Visibility GDMonoMethod::get_visibility() { } } -MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc) { - if (get_return_type().type_encoding != MONO_TYPE_VOID || get_parameters_count() > 0) { - MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), get_parameters_count()); +MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc) const { + MonoException *exc = nullptr; + MonoObject *ret; + + if (params_count > 0) { + MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), params_count); for (int i = 0; i < params_count; i++) { MonoObject *boxed_param = GDMonoMarshal::variant_to_mono_object(p_params[i], param_types[i]); mono_array_setref(params, i, boxed_param); } - MonoException *exc = NULL; - MonoObject *ret = GDMonoUtils::runtime_invoke_array(mono_method, p_object, params, &exc); - - if (exc) { - ret = NULL; - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } - - return ret; + ret = GDMonoUtils::runtime_invoke_array(mono_method, p_object, params, &exc); } else { - MonoException *exc = NULL; - GDMonoUtils::runtime_invoke(mono_method, p_object, NULL, &exc); - - if (exc) { - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } + ret = GDMonoUtils::runtime_invoke(mono_method, p_object, nullptr, &exc); + } - return NULL; + if (exc) { + ret = nullptr; + if (r_exc) { + *r_exc = exc; + } else { + GDMonoUtils::set_pending_exception(exc); + } } + + return ret; } -MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoException **r_exc) { - ERR_FAIL_COND_V(get_parameters_count() > 0, NULL); - return invoke_raw(p_object, NULL, r_exc); +MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoException **r_exc) const { + ERR_FAIL_COND_V(get_parameters_count() > 0, nullptr); + return invoke_raw(p_object, nullptr, r_exc); } -MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc) { - MonoException *exc = NULL; +MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc) const { + MonoException *exc = nullptr; MonoObject *ret = GDMonoUtils::runtime_invoke(mono_method, p_object, p_params, &exc); if (exc) { - ret = NULL; + ret = nullptr; if (r_exc) { *r_exc = exc; } else { @@ -173,19 +164,19 @@ bool GDMonoMethod::has_attribute(GDMonoClass *p_attr_class) { } MonoObject *GDMonoMethod::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, NULL); + ERR_FAIL_NULL_V(p_attr_class, nullptr); if (!attrs_fetched) fetch_attributes(); if (!attributes) - return NULL; + return nullptr; return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); } void GDMonoMethod::fetch_attributes() { - ERR_FAIL_COND(attributes != NULL); + ERR_FAIL_COND(attributes != nullptr); attributes = mono_custom_attrs_from_method(mono_method); attrs_fetched = true; } @@ -247,7 +238,7 @@ void GDMonoMethod::get_parameter_names(Vector<StringName> &names) const { } void GDMonoMethod::get_parameter_types(Vector<ManagedType> &types) const { - for (int i = 0; i < param_types.size(); ++i) { + for (int i = 0; i < params_count; ++i) { types.push_back(param_types[i]); } } @@ -256,13 +247,22 @@ const MethodInfo &GDMonoMethod::get_method_info() { if (!method_info_fetched) { method_info.name = name; - method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type), ""); + + bool nil_is_variant = false; + method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type, &nil_is_variant), ""); + if (method_info.return_val.type == Variant::NIL && nil_is_variant) + method_info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; Vector<StringName> names; get_parameter_names(names); for (int i = 0; i < params_count; ++i) { - method_info.arguments.push_back(PropertyInfo(GDMonoMarshal::managed_to_variant_type(param_types[i]), names[i])); + nil_is_variant = false; + PropertyInfo arg_info = PropertyInfo(GDMonoMarshal::managed_to_variant_type(param_types[i], &nil_is_variant), names[i]); + if (arg_info.type == Variant::NIL && nil_is_variant) + arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + + method_info.arguments.push_back(arg_info); } // TODO: default arguments @@ -281,7 +281,7 @@ GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) { method_info_fetched = false; attrs_fetched = false; - attributes = NULL; + attributes = nullptr; _update_signature(); } diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h index b47e42dec2..54b2eba3e8 100644 --- a/modules/mono/mono_gd/gd_mono_method.h +++ b/modules/mono/mono_gd/gd_mono_method.h @@ -57,28 +57,28 @@ class GDMonoMethod : public IMonoClassMember { MonoMethod *mono_method; public: - virtual GDMonoClass *get_enclosing_class() const GD_FINAL; + virtual GDMonoClass *get_enclosing_class() const final; - virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_METHOD; } + virtual MemberType get_member_type() const final { return MEMBER_TYPE_METHOD; } - virtual StringName get_name() const GD_FINAL { return name; } + virtual StringName get_name() const final { return name; } - virtual bool is_static() GD_FINAL; + virtual bool is_static() final; - virtual Visibility get_visibility() GD_FINAL; + virtual Visibility get_visibility() final; - virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL; + virtual bool has_attribute(GDMonoClass *p_attr_class) final; + virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final; void fetch_attributes(); - _FORCE_INLINE_ MonoMethod *get_mono_ptr() { return mono_method; } + _FORCE_INLINE_ MonoMethod *get_mono_ptr() const { return mono_method; } - _FORCE_INLINE_ int get_parameters_count() { return params_count; } - _FORCE_INLINE_ ManagedType get_return_type() { return return_type; } + _FORCE_INLINE_ int get_parameters_count() const { return params_count; } + _FORCE_INLINE_ ManagedType get_return_type() const { return return_type; } - MonoObject *invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc = NULL); - MonoObject *invoke(MonoObject *p_object, MonoException **r_exc = NULL); - MonoObject *invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc = NULL); + MonoObject *invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc = nullptr) const; + MonoObject *invoke(MonoObject *p_object, MonoException **r_exc = nullptr) const; + MonoObject *invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc = nullptr) const; String get_full_name(bool p_signature = false) const; String get_full_name_no_class() const; diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h index d8c9a5eb02..0e05e974e9 100644 --- a/modules/mono/mono_gd/gd_mono_method_thunk.h +++ b/modules/mono/mono_gd/gd_mono_method_thunk.h @@ -39,7 +39,7 @@ #include "gd_mono_method.h" #include "gd_mono_utils.h" -#if !defined(JAVASCRIPT_ENABLED) +#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED) #define HAVE_METHOD_THUNKS #endif @@ -60,16 +60,16 @@ public: } _FORCE_INLINE_ bool is_null() { - return mono_method_thunk == NULL; + return mono_method_thunk == nullptr; } _FORCE_INLINE_ void nullify() { - mono_method_thunk = NULL; + mono_method_thunk = nullptr; } _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { #ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method == nullptr); CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID); if (p_mono_method->is_static()) { @@ -82,7 +82,7 @@ public: } GDMonoMethodThunk() : - mono_method_thunk(NULL) { + mono_method_thunk(nullptr) { } explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { @@ -106,16 +106,16 @@ public: } _FORCE_INLINE_ bool is_null() { - return mono_method_thunk == NULL; + return mono_method_thunk == nullptr; } _FORCE_INLINE_ void nullify() { - mono_method_thunk = NULL; + mono_method_thunk = nullptr; } _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { #ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method == nullptr); CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID); if (p_mono_method->is_static()) { @@ -128,12 +128,12 @@ public: } GDMonoMethodThunkR() : - mono_method_thunk(NULL) { + mono_method_thunk(nullptr) { } explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { #ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method == nullptr); #endif mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); } @@ -146,7 +146,7 @@ struct VariadicInvokeMonoMethodImpl { static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) { if (p_mono_method->is_static()) { void *args[ThunkParamCount] = { p_arg1, p_args... }; - p_mono_method->invoke_raw(NULL, args, r_exc); + p_mono_method->invoke_raw(nullptr, args, r_exc); } else { void *args[ThunkParamCount] = { p_args... }; p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc); @@ -167,7 +167,7 @@ struct VariadicInvokeMonoMethod<0> { #ifdef DEBUG_ENABLED CRASH_COND(!p_mono_method->is_static()); #endif - p_mono_method->invoke_raw(NULL, NULL, r_exc); + p_mono_method->invoke_raw(nullptr, nullptr, r_exc); } }; @@ -176,9 +176,9 @@ struct VariadicInvokeMonoMethod<1, P1> { static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) { if (p_mono_method->is_static()) { void *args[1] = { p_arg1 }; - p_mono_method->invoke_raw(NULL, args, r_exc); + p_mono_method->invoke_raw(nullptr, args, r_exc); } else { - p_mono_method->invoke_raw((MonoObject *)p_arg1, NULL, r_exc); + p_mono_method->invoke_raw((MonoObject *)p_arg1, nullptr, r_exc); } } }; @@ -203,7 +203,7 @@ struct VariadicInvokeMonoMethodRImpl { static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) { if (p_mono_method->is_static()) { void *args[ThunkParamCount] = { p_arg1, p_args... }; - MonoObject *r = p_mono_method->invoke_raw(NULL, args, r_exc); + MonoObject *r = p_mono_method->invoke_raw(nullptr, args, r_exc); return unbox_if_needed<R>(r, p_mono_method->get_return_type()); } else { void *args[ThunkParamCount] = { p_args... }; @@ -226,7 +226,7 @@ struct VariadicInvokeMonoMethodR<0, R> { #ifdef DEBUG_ENABLED CRASH_COND(!p_mono_method->is_static()); #endif - MonoObject *r = p_mono_method->invoke_raw(NULL, NULL, r_exc); + MonoObject *r = p_mono_method->invoke_raw(nullptr, nullptr, r_exc); return unbox_if_needed<R>(r, p_mono_method->get_return_type()); } }; @@ -236,10 +236,10 @@ struct VariadicInvokeMonoMethodR<1, R, P1> { static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) { if (p_mono_method->is_static()) { void *args[1] = { p_arg1 }; - MonoObject *r = p_mono_method->invoke_raw(NULL, args, r_exc); + MonoObject *r = p_mono_method->invoke_raw(nullptr, args, r_exc); return unbox_if_needed<R>(r, p_mono_method->get_return_type()); } else { - MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, NULL, r_exc); + MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, nullptr, r_exc); return unbox_if_needed<R>(r, p_mono_method->get_return_type()); } } @@ -256,16 +256,16 @@ public: } _FORCE_INLINE_ bool is_null() { - return mono_method == NULL; + return mono_method == nullptr; } _FORCE_INLINE_ void nullify() { - mono_method = NULL; + mono_method = nullptr; } _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { #ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method == nullptr); CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID); if (p_mono_method->is_static()) { @@ -278,7 +278,7 @@ public: } GDMonoMethodThunk() : - mono_method(NULL) { + mono_method(nullptr) { } explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { @@ -297,16 +297,16 @@ public: } _FORCE_INLINE_ bool is_null() { - return mono_method == NULL; + return mono_method == nullptr; } _FORCE_INLINE_ void nullify() { - mono_method = NULL; + mono_method = nullptr; } _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { #ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == NULL); + CRASH_COND(p_mono_method == nullptr); CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID); if (p_mono_method->is_static()) { @@ -319,7 +319,7 @@ public: } GDMonoMethodThunkR() : - mono_method(NULL) { + mono_method(nullptr) { } explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp index 3b5ce58d80..c3e7598f2d 100644 --- a/modules/mono/mono_gd/gd_mono_property.cpp +++ b/modules/mono/mono_gd/gd_mono_property.cpp @@ -57,7 +57,7 @@ GDMonoProperty::GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_own MonoMethodSignature *setter_sig = mono_method_signature(prop_method); - void *iter = NULL; + void *iter = nullptr; MonoType *param_raw_type = mono_signature_get_params(setter_sig, &iter); type.type_encoding = mono_type_get_type(param_raw_type); @@ -66,7 +66,7 @@ GDMonoProperty::GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_own } attrs_fetched = false; - attributes = NULL; + attributes = nullptr; } GDMonoProperty::~GDMonoProperty() { @@ -77,17 +77,17 @@ GDMonoProperty::~GDMonoProperty() { bool GDMonoProperty::is_static() { MonoMethod *prop_method = mono_property_get_get_method(mono_property); - if (prop_method == NULL) + if (prop_method == nullptr) prop_method = mono_property_get_set_method(mono_property); - return mono_method_get_flags(prop_method, NULL) & MONO_METHOD_ATTR_STATIC; + return mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_STATIC; } IMonoClassMember::Visibility GDMonoProperty::get_visibility() { MonoMethod *prop_method = mono_property_get_get_method(mono_property); - if (prop_method == NULL) + if (prop_method == nullptr) prop_method = mono_property_get_set_method(mono_property); - switch (mono_method_get_flags(prop_method, NULL) & MONO_METHOD_ATTR_ACCESS_MASK) { + switch (mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) { case MONO_METHOD_ATTR_PRIVATE: return IMonoClassMember::PRIVATE; case MONO_METHOD_ATTR_FAM_AND_ASSEM: @@ -116,36 +116,36 @@ bool GDMonoProperty::has_attribute(GDMonoClass *p_attr_class) { } MonoObject *GDMonoProperty::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, NULL); + ERR_FAIL_NULL_V(p_attr_class, nullptr); if (!attrs_fetched) fetch_attributes(); if (!attributes) - return NULL; + return nullptr; return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); } void GDMonoProperty::fetch_attributes() { - ERR_FAIL_COND(attributes != NULL); + ERR_FAIL_COND(attributes != nullptr); attributes = mono_custom_attrs_from_property(owner->get_mono_ptr(), mono_property); attrs_fetched = true; } bool GDMonoProperty::has_getter() { - return mono_property_get_get_method(mono_property) != NULL; + return mono_property_get_get_method(mono_property) != nullptr; } bool GDMonoProperty::has_setter() { - return mono_property_get_set_method(mono_property) != NULL; + return mono_property_get_set_method(mono_property) != nullptr; } void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) { MonoMethod *prop_method = mono_property_get_set_method(mono_property); MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1); mono_array_setref(params, 0, p_value); - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::runtime_invoke_array(prop_method, p_object, params, &exc); if (exc) { if (r_exc) { @@ -157,7 +157,7 @@ void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoEx } void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoException **r_exc) { - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::property_set_value(mono_property, p_object, p_params, &exc); if (exc) { @@ -170,11 +170,11 @@ void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoExcept } MonoObject *GDMonoProperty::get_value(MonoObject *p_object, MonoException **r_exc) { - MonoException *exc = NULL; - MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, NULL, &exc); + MonoException *exc = nullptr; + MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, nullptr, &exc); if (exc) { - ret = NULL; + ret = nullptr; if (r_exc) { *r_exc = exc; } else { diff --git a/modules/mono/mono_gd/gd_mono_property.h b/modules/mono/mono_gd/gd_mono_property.h index 692037f76a..4653758a86 100644 --- a/modules/mono/mono_gd/gd_mono_property.h +++ b/modules/mono/mono_gd/gd_mono_property.h @@ -47,17 +47,17 @@ class GDMonoProperty : public IMonoClassMember { MonoCustomAttrInfo *attributes; public: - virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; } + virtual GDMonoClass *get_enclosing_class() const final { return owner; } - virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_PROPERTY; } + virtual MemberType get_member_type() const final { return MEMBER_TYPE_PROPERTY; } - virtual StringName get_name() const GD_FINAL { return name; } + virtual StringName get_name() const final { return name; } - virtual bool is_static() GD_FINAL; - virtual Visibility get_visibility() GD_FINAL; + virtual bool is_static() final; + virtual Visibility get_visibility() final; - virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL; + virtual bool has_attribute(GDMonoClass *p_attr_class) final; + virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final; void fetch_attributes(); bool has_getter(); @@ -65,9 +65,9 @@ public: _FORCE_INLINE_ ManagedType get_type() const { return type; } - void set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc = NULL); - void set_value(MonoObject *p_object, void **p_params, MonoException **r_exc = NULL); - MonoObject *get_value(MonoObject *p_object, MonoException **r_exc = NULL); + void set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc = nullptr); + void set_value(MonoObject *p_object, void **p_params, MonoException **r_exc = nullptr); + MonoObject *get_value(MonoObject *p_object, MonoException **r_exc = nullptr); bool get_bool_value(MonoObject *p_object); int get_int_value(MonoObject *p_object); diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 41f49d8ac9..00119ced88 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -32,6 +32,8 @@ #include <mono/metadata/exception.h> +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/os/dir_access.h" #include "core/os/mutex.h" #include "core/os/os.h" @@ -39,7 +41,7 @@ #include "core/reference.h" #ifdef TOOLS_ENABLED -#include "editor/debugger/script_editor_debugger.h" +#include "editor/debugger/editor_debugger_node.h" #endif #include "../csharp_script.h" @@ -55,7 +57,7 @@ namespace GDMonoUtils { MonoObject *unmanaged_get_managed(Object *unmanaged) { if (!unmanaged) - return NULL; + return nullptr; if (unmanaged->get_script_instance()) { CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance()); @@ -69,7 +71,7 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); - ERR_FAIL_NULL_V(data, NULL); + ERR_FAIL_NULL_V(data, nullptr); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value(); @@ -80,14 +82,13 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { // Already had a binding that needs to be setup CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, unmanaged); - ERR_FAIL_COND_V(!script_binding.inited, NULL); + ERR_FAIL_COND_V(!script_binding.inited, nullptr); } } - Ref<MonoGCHandle> &gchandle = script_binding.gchandle; - ERR_FAIL_COND_V(gchandle.is_null(), NULL); + MonoGCHandleData &gchandle = script_binding.gchandle; - MonoObject *target = gchandle->get_target(); + MonoObject *target = gchandle.get_target(); if (target) return target; @@ -98,13 +99,13 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { #ifdef DEBUG_ENABLED CRASH_COND(script_binding.type_name == StringName()); - CRASH_COND(script_binding.wrapper_class == NULL); + CRASH_COND(script_binding.wrapper_class == nullptr); #endif MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged); - ERR_FAIL_NULL_V(mono_object, NULL); + ERR_FAIL_NULL_V(mono_object, nullptr); - gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE); + gchandle = MonoGCHandleData::new_strong_handle(mono_object); // Tie managed to unmanaged Reference *ref = Object::cast_to<Reference>(unmanaged); @@ -126,10 +127,15 @@ void set_main_thread(MonoThread *p_thread) { } MonoThread *attach_current_thread() { - ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), NULL); + ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), nullptr); MonoDomain *scripts_domain = GDMono::get_singleton()->get_scripts_domain(); +#ifndef GD_MONO_SINGLE_APPDOMAIN MonoThread *mono_thread = mono_thread_attach(scripts_domain ? scripts_domain : mono_get_root_domain()); - ERR_FAIL_NULL_V(mono_thread, NULL); +#else + // The scripts domain is the root domain + MonoThread *mono_thread = mono_thread_attach(scripts_domain); +#endif + ERR_FAIL_NULL_V(mono_thread, nullptr); return mono_thread; } @@ -151,13 +157,36 @@ MonoThread *get_current_thread() { } bool is_thread_attached() { - return mono_domain_get() != NULL; + return mono_domain_get() != nullptr; +} + +uint32_t new_strong_gchandle(MonoObject *p_object) { + return mono_gchandle_new(p_object, /* pinned: */ false); +} + +uint32_t new_strong_gchandle_pinned(MonoObject *p_object) { + return mono_gchandle_new(p_object, /* pinned: */ true); +} + +uint32_t new_weak_gchandle(MonoObject *p_object) { + return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false); +} + +void free_gchandle(uint32_t p_gchandle) { + mono_gchandle_free(p_gchandle); } void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) { GDMonoMethod *ctor = p_class->get_method(".ctor", 0); ERR_FAIL_NULL(ctor); - ctor->invoke_raw(p_this_obj, NULL, r_exc); + ctor->invoke_raw(p_this_obj, nullptr, r_exc); +} + +bool mono_delegate_equal(MonoDelegate *p_a, MonoDelegate *p_b) { + MonoException *exc = nullptr; + MonoBoolean res = CACHED_METHOD_THUNK(Delegate, Equals).invoke((MonoObject *)p_a, (MonoObject *)p_b, &exc); + UNHANDLED_EXCEPTION(exc); + return (bool)res; } GDMonoClass *get_object_class(MonoObject *p_object) { @@ -197,18 +226,18 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) { if (assembly == GDMono::get_singleton()->get_editor_api_assembly()) return klass; #endif - } while ((klass = klass->get_parent_class()) != NULL); + } while ((klass = klass->get_parent_class()) != nullptr); - return NULL; + return nullptr; } MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) { bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), p_native); - ERR_FAIL_COND_V_MSG(!parent_is_object_class, NULL, + ERR_FAIL_COND_V_MSG(!parent_is_object_class, nullptr, "Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'."); MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, NULL); + ERR_FAIL_NULL_V(mono_object, nullptr); CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); @@ -218,9 +247,21 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa return mono_object; } +MonoObject *create_managed_from(const StringName &p_from) { + MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(StringName)); + ERR_FAIL_NULL_V(mono_object, nullptr); + + // Construct + GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(StringName)); + + CACHED_FIELD(StringName, ptr)->set_value_raw(mono_object, memnew(StringName(p_from))); + + return mono_object; +} + MonoObject *create_managed_from(const NodePath &p_from) { MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath)); - ERR_FAIL_NULL_V(mono_object, NULL); + ERR_FAIL_NULL_V(mono_object, nullptr); // Construct GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(NodePath)); @@ -232,7 +273,7 @@ MonoObject *create_managed_from(const NodePath &p_from) { MonoObject *create_managed_from(const RID &p_from) { MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID)); - ERR_FAIL_NULL_V(mono_object, NULL); + ERR_FAIL_NULL_V(mono_object, nullptr); // Construct GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(RID)); @@ -244,15 +285,15 @@ MonoObject *create_managed_from(const RID &p_from) { MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, NULL); + ERR_FAIL_NULL_V(mono_object, nullptr); // Search constructor that takes a pointer as parameter MonoMethod *m; - void *iter = NULL; + void *iter = nullptr; while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { if (strcmp(mono_method_get_name(m), ".ctor") == 0) { MonoMethodSignature *sig = mono_method_signature(m); - void *front = NULL; + void *front = nullptr; if (mono_signature_get_param_count(sig) == 1 && mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { break; @@ -260,12 +301,12 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { } } - CRASH_COND(m == NULL); + CRASH_COND(m == nullptr); Array *new_array = memnew(Array(p_from)); void *args[1] = { &new_array }; - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); UNHANDLED_EXCEPTION(exc); @@ -274,15 +315,15 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) { MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, NULL); + ERR_FAIL_NULL_V(mono_object, nullptr); // Search constructor that takes a pointer as parameter MonoMethod *m; - void *iter = NULL; + void *iter = nullptr; while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { if (strcmp(mono_method_get_name(m), ".ctor") == 0) { MonoMethodSignature *sig = mono_method_signature(m); - void *front = NULL; + void *front = nullptr; if (mono_signature_get_param_count(sig) == 1 && mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { break; @@ -290,12 +331,12 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) } } - CRASH_COND(m == NULL); + CRASH_COND(m == nullptr); Dictionary *new_dict = memnew(Dictionary(p_from)); void *args[1] = { &new_dict }; - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); UNHANDLED_EXCEPTION(exc); @@ -305,7 +346,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) MonoDomain *create_domain(const String &p_friendly_name) { print_verbose("Mono: Creating domain '" + p_friendly_name + "'..."); - MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL); + MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), nullptr); if (domain) { // Workaround to avoid this exception: @@ -330,7 +371,7 @@ String get_exception_name_and_message(MonoException *p_exc) { res += ": "; MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); - MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, NULL, NULL); + MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, nullptr, nullptr); res += GDMonoMarshal::mono_string_to_godot(msg); return res; @@ -341,7 +382,7 @@ void set_exception_message(MonoException *p_exc, String message) { MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); MonoString *msg = GDMonoMarshal::mono_string_from_godot(message); void *params[1] = { msg }; - property_set_value(prop, (MonoObject *)p_exc, params, NULL); + property_set_value(prop, (MonoObject *)p_exc, params, nullptr); } void debug_print_unhandled_exception(MonoException *p_exc) { @@ -351,7 +392,7 @@ void debug_print_unhandled_exception(MonoException *p_exc) { void debug_send_unhandled_exception_error(MonoException *p_exc) { #ifdef DEBUG_ENABLED - if (!ScriptDebugger::get_singleton()) { + if (!EngineDebugger::is_active()) { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { ERR_PRINT(GDMonoUtils::get_exception_name_and_message(p_exc)); @@ -360,7 +401,11 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { return; } - _TLS_RECURSION_GUARD_; + static thread_local bool _recursion_flag_ = false; + if (_recursion_flag_) + return; + _recursion_flag_ = true; + SCOPE_EXIT { _recursion_flag_ = false; }; ScriptLanguage::StackInfo separator; separator.file = String(); @@ -370,14 +415,14 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { Vector<ScriptLanguage::StackInfo> si; String exc_msg; - while (p_exc != NULL) { + while (p_exc != nullptr) { GDMonoClass *st_klass = CACHED_CLASS(System_Diagnostics_StackTrace); MonoObject *stack_trace = mono_object_new(mono_domain_get(), st_klass->get_mono_ptr()); MonoBoolean need_file_info = true; void *ctor_args[2] = { p_exc, &need_file_info }; - MonoException *unexpected_exc = NULL; + MonoException *unexpected_exc = nullptr; CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc); if (unexpected_exc) { @@ -386,7 +431,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { } Vector<ScriptLanguage::StackInfo> _si; - if (stack_trace != NULL) { + if (stack_trace != nullptr) { _si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace); for (int i = _si.size() - 1; i >= 0; i--) si.insert(0, _si[i]); @@ -396,10 +441,10 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { GDMonoClass *exc_class = GDMono::get_singleton()->get_class(mono_get_exception_class()); GDMonoProperty *inner_exc_prop = exc_class->get_property("InnerException"); - CRASH_COND(inner_exc_prop == NULL); + CRASH_COND(inner_exc_prop == nullptr); MonoObject *inner_exc = inner_exc_prop->get_value((MonoObject *)p_exc); - if (inner_exc != NULL) + if (inner_exc != nullptr) si.insert(0, separator); p_exc = (MonoException *)inner_exc; @@ -410,7 +455,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { int line = si.size() ? si[0].line : __LINE__; String error_msg = "Unhandled exception"; - ScriptDebugger::get_singleton()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si); + EngineDebugger::get_script_debugger()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si); #endif } @@ -437,8 +482,7 @@ void set_pending_exception(MonoException *p_exc) { #endif } -_THREAD_LOCAL_(int) -current_invoke_count = 0; +thread_local int current_invoke_count = 0; MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc) { GD_MONO_BEGIN_RUNTIME_INVOKE; @@ -526,7 +570,7 @@ namespace Marshal { bool type_is_generic_array(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; @@ -534,27 +578,27 @@ bool type_is_generic_array(MonoReflectionType *p_reftype) { bool type_is_generic_dictionary(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; } void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) { - MonoException *exc = NULL; + MonoException *exc = nullptr; CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType).invoke(p_array_reftype, r_elem_reftype, &exc); UNHANDLED_EXCEPTION(exc); } void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { - MonoException *exc = NULL; + MonoException *exc = nullptr; CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes).invoke(p_dict_reftype, r_key_reftype, r_value_reftype, &exc); UNHANDLED_EXCEPTION(exc); } bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; @@ -562,7 +606,7 @@ bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) { bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) { NO_GLUE_RET(false); - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType).invoke(p_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; @@ -570,7 +614,7 @@ bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) { bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) { NO_GLUE_RET(false); - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info).invoke(p_reftype, r_elem_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; @@ -578,7 +622,7 @@ bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoR bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { NO_GLUE_RET(false); - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info).invoke(p_reftype, r_key_reftype, r_value_reftype, &exc); UNHANDLED_EXCEPTION(exc); return (bool)res; @@ -587,7 +631,7 @@ bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoR Array enumerable_to_array(MonoObject *p_enumerable) { NO_GLUE_RET(Array()); Array result; - MonoException *exc = NULL; + MonoException *exc = nullptr; CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray).invoke(p_enumerable, &result, &exc); UNHANDLED_EXCEPTION(exc); return result; @@ -596,7 +640,7 @@ Array enumerable_to_array(MonoObject *p_enumerable) { Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) { NO_GLUE_RET(Dictionary()); Dictionary result; - MonoException *exc = NULL; + MonoException *exc = nullptr; CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary).invoke(p_idictionary, &result, &exc); UNHANDLED_EXCEPTION(exc); return result; @@ -605,23 +649,23 @@ Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) { Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary) { NO_GLUE_RET(Dictionary()); Dictionary result; - MonoException *exc = NULL; + MonoException *exc = nullptr; CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary).invoke(p_generic_idictionary, &result, &exc); UNHANDLED_EXCEPTION(exc); return result; } GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) { - NO_GLUE_RET(NULL); - MonoException *exc = NULL; + NO_GLUE_RET(nullptr); + MonoException *exc = nullptr; MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType).invoke(p_elem_reftype, &exc); UNHANDLED_EXCEPTION(exc); return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); } GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { - NO_GLUE_RET(NULL); - MonoException *exc = NULL; + NO_GLUE_RET(nullptr); + MonoException *exc = nullptr; MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType).invoke(p_key_reftype, p_value_reftype, &exc); UNHANDLED_EXCEPTION(exc); return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); @@ -630,7 +674,7 @@ GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, Mon } // namespace Marshal ScopeThreadAttach::ScopeThreadAttach() : - mono_thread(NULL) { + mono_thread(nullptr) { if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) { mono_thread = GDMonoUtils::attach_current_thread(); } @@ -642,6 +686,10 @@ ScopeThreadAttach::~ScopeThreadAttach() { } } -// namespace Marshal +StringName get_native_godot_class_name(GDMonoClass *p_class) { + MonoObject *native_name_obj = p_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(nullptr); + StringName *ptr = GDMonoMarshal::unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(native_name_obj)); + return ptr ? *ptr : StringName(); +} } // namespace GDMonoUtils diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index db9f99bfdc..b850e1be9b 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -35,14 +35,13 @@ #include "../mono_gc_handle.h" #include "../utils/macros.h" -#include "../utils/thread_local.h" #include "gd_mono_header.h" #include "core/object.h" #include "core/reference.h" #define UNHANDLED_EXCEPTION(m_exc) \ - if (unlikely(m_exc != NULL)) { \ + if (unlikely(m_exc != nullptr)) { \ GDMonoUtils::debug_unhandled_exception(m_exc); \ GD_UNREACHABLE(); \ } @@ -78,7 +77,7 @@ _FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) /** * If the object has a csharp script, returns the target of the gchandle stored in the script instance * Otherwise returns a newly constructed MonoObject* which is attached to the object - * Returns NULL on error + * Returns nullptr on error */ MonoObject *unmanaged_get_managed(Object *unmanaged); @@ -90,10 +89,17 @@ MonoThread *get_current_thread(); bool is_thread_attached(); _FORCE_INLINE_ bool is_main_thread() { - return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current(); + return mono_domain_get() != nullptr && mono_thread_get_main() == mono_thread_current(); } -void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = NULL); +uint32_t new_strong_gchandle(MonoObject *p_object); +uint32_t new_strong_gchandle_pinned(MonoObject *p_object); +uint32_t new_weak_gchandle(MonoObject *p_object); +void free_gchandle(uint32_t p_gchandle); + +void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = nullptr); + +bool mono_delegate_equal(MonoDelegate *p_a, MonoDelegate *p_b); GDMonoClass *get_object_class(MonoObject *p_object); GDMonoClass *type_get_proxy_class(const StringName &p_type); @@ -101,6 +107,7 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class); MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object); +MonoObject *create_managed_from(const StringName &p_from); MonoObject *create_managed_from(const NodePath &p_from); MonoObject *create_managed_from(const RID &p_from); MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class); @@ -123,7 +130,7 @@ void print_unhandled_exception(MonoException *p_exc); */ void set_pending_exception(MonoException *p_exc); -extern _THREAD_LOCAL_(int) current_invoke_count; +extern thread_local int current_invoke_count; _FORCE_INLINE_ int get_runtime_invoke_count() { return current_invoke_count; @@ -152,9 +159,11 @@ private: MonoThread *mono_thread; }; +StringName get_native_godot_class_name(GDMonoClass *p_class); + } // namespace GDMonoUtils -#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL))) +#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoUtils::get_native_godot_class_name(m_class)) #define GD_MONO_BEGIN_RUNTIME_INVOKE \ int &_runtime_invoke_count_ref = GDMonoUtils::get_runtime_invoke_count_ref(); \ diff --git a/modules/mono/mono_gd/managed_type.h b/modules/mono/mono_gd/managed_type.h index 11b832d0cc..84d1837853 100644 --- a/modules/mono/mono_gd/managed_type.h +++ b/modules/mono/mono_gd/managed_type.h @@ -46,7 +46,7 @@ struct ManagedType { ManagedType() : type_encoding(0), - type_class(NULL) { + type_class(nullptr) { } ManagedType(int p_type_encoding, GDMonoClass *p_type_class) : diff --git a/modules/mono/mono_gd/gd_mono_android.cpp b/modules/mono/mono_gd/support/android_support.cpp index 761368878f..8bcdeec9dd 100644..100755 --- a/modules/mono/mono_gd/gd_mono_android.cpp +++ b/modules/mono/mono_gd/support/android_support.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gd_mono_android.cpp */ +/* android_support.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "gd_mono_android.h" +#include "android_support.h" #if defined(ANDROID_ENABLED) @@ -49,14 +49,16 @@ #include "platform/android/os_android.h" #include "platform/android/thread_jandroid.h" -#include "../utils/path_utils.h" -#include "../utils/string_utils.h" -#include "gd_mono_cache.h" -#include "gd_mono_marshal.h" +#include "../../utils/path_utils.h" +#include "../../utils/string_utils.h" +#include "../gd_mono_cache.h" +#include "../gd_mono_marshal.h" // Warning: JNI boilerplate ahead... continue at your own risk -namespace GDMonoAndroid { +namespace gdmono { +namespace android { +namespace support { template <typename T> struct ScopedLocalRef { @@ -67,7 +69,7 @@ struct ScopedLocalRef { _FORCE_INLINE_ operator T() const { return local_ref; } _FORCE_INLINE_ operator jvalue() const { return (jvalue)local_ref; } - _FORCE_INLINE_ operator bool() const { return local_ref != NULL; } + _FORCE_INLINE_ operator bool() const { return local_ref != nullptr; } _FORCE_INLINE_ bool operator==(std::nullptr_t) const { return local_ref == nullptr; @@ -122,7 +124,7 @@ String determine_app_native_lib_dir() { String result; - const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, NULL); + const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, nullptr); if (nativeLibraryDirUtf8) { result.parse_utf8(nativeLibraryDirUtf8); env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDirUtf8); @@ -150,21 +152,21 @@ int gd_mono_convert_dl_flags(int flags) { return lflags; } -#ifndef GD_MONO_ANDROID_SO_NAME -#define GD_MONO_ANDROID_SO_NAME "libmonosgen-2.0.so" +#ifndef GD_MONO_SO_NAME +#define GD_MONO_SO_NAME "libmonosgen-2.0.so" #endif -const char *mono_so_name = GD_MONO_ANDROID_SO_NAME; +const char *mono_so_name = GD_MONO_SO_NAME; const char *godot_so_name = "libgodot_android.so"; -void *mono_dl_handle = NULL; -void *godot_dl_handle = NULL; +void *mono_dl_handle = nullptr; +void *godot_dl_handle = nullptr; void *try_dlopen(const String &p_so_path, int p_flags) { if (!FileAccess::exists(p_so_path)) { if (OS::get_singleton()->is_stdout_verbose()) OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data()); - return NULL; + return nullptr; } int lflags = gd_mono_convert_dl_flags(p_flags); @@ -174,7 +176,7 @@ void *try_dlopen(const String &p_so_path, int p_flags) { if (!handle) { if (OS::get_singleton()->is_stdout_verbose()) OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror()); - return NULL; + return nullptr; } if (OS::get_singleton()->is_stdout_verbose()) @@ -184,7 +186,7 @@ void *try_dlopen(const String &p_so_path, int p_flags) { } void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) { - if (p_name == NULL) { + if (p_name == nullptr) { // __Internal if (!mono_dl_handle) { @@ -209,7 +211,7 @@ void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void return try_dlopen(so_path, p_flags); } - return NULL; + return nullptr; } void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) { @@ -230,7 +232,7 @@ void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, vo if (r_err) *r_err = str_format_new("%s\n", dlerror()); - return NULL; + return nullptr; } void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) { @@ -238,9 +240,9 @@ void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) { // Not sure if this ever happens. Does Mono close the handle for the main module? if (p_handle == mono_dl_handle) - mono_dl_handle = NULL; + mono_dl_handle = nullptr; - return NULL; + return nullptr; } int32_t build_version_sdk_int = 0; @@ -265,7 +267,7 @@ int32_t get_build_version_sdk_int() { return build_version_sdk_int; } -jobject certStore = NULL; // KeyStore +jobject certStore = nullptr; // KeyStore MonoBoolean _gd_mono_init_cert_store() { // The JNI code is the equivalent of: @@ -293,7 +295,7 @@ MonoBoolean _gd_mono_init_cert_store() { if (jni_exception_check(env)) return 0; - env->CallVoidMethod(certStoreLocal, load, NULL); + env->CallVoidMethod(certStoreLocal, load, nullptr); if (jni_exception_check(env)) return 0; @@ -317,7 +319,7 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { if (!mono_error_ok(&mono_error)) { ERR_PRINT(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'."); mono_error_cleanup(&mono_error); - return NULL; + return nullptr; } JNIEnv *env = ThreadAndroid::get_env(); @@ -326,20 +328,20 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { mono_free(alias_utf8); ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore")); - ERR_FAIL_NULL_V(keyStoreClass, NULL); + ERR_FAIL_NULL_V(keyStoreClass, nullptr); ScopedLocalRef<jclass> certificateClass(env, env->FindClass("java/security/cert/Certificate")); - ERR_FAIL_NULL_V(certificateClass, NULL); + ERR_FAIL_NULL_V(certificateClass, nullptr); jmethodID getCertificate = env->GetMethodID(keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;"); - ERR_FAIL_NULL_V(getCertificate, NULL); + ERR_FAIL_NULL_V(getCertificate, nullptr); jmethodID getEncoded = env->GetMethodID(certificateClass, "getEncoded", "()[B"); - ERR_FAIL_NULL_V(getEncoded, NULL); + ERR_FAIL_NULL_V(getEncoded, nullptr); ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get())); if (!certificate) - return NULL; + return nullptr; ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded)); jsize encodedLength = env->GetArrayLength(encoded); @@ -352,11 +354,16 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { return encoded_ret; } +void register_internal_calls() { + mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store); + mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup); +} + void initialize() { // We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls"); - mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, NULL); + mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, nullptr); String app_native_lib_dir = get_app_native_lib_dir(); String so_path = path::join(app_native_lib_dir, godot_so_name); @@ -364,31 +371,28 @@ void initialize() { godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY)); } -void register_internal_calls() { - mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store); - mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup); -} - void cleanup() { // This is called after shutting down the Mono runtime if (mono_dl_handle) - gd_mono_android_dlclose(mono_dl_handle, NULL); + gd_mono_android_dlclose(mono_dl_handle, nullptr); if (godot_dl_handle) - gd_mono_android_dlclose(godot_dl_handle, NULL); + gd_mono_android_dlclose(godot_dl_handle, nullptr); JNIEnv *env = ThreadAndroid::get_env(); if (certStore) { env->DeleteGlobalRef(certStore); - certStore = NULL; + certStore = nullptr; } } -} // namespace GDMonoAndroid +} // namespace support +} // namespace android +} // namespace gdmono -using namespace GDMonoAndroid; +using namespace gdmono::android::support; // The following are P/Invoke functions required by the monodroid profile of the BCL. // These are P/Invoke functions and not internal calls, hence why they use @@ -417,7 +421,7 @@ GD_PINVOKE_EXPORT int32_t monodroid_get_system_property(const char *p_name, char memcpy(*r_value, prop_value_str, len); (*r_value)[len] = '\0'; } else { - *r_value = NULL; + *r_value = nullptr; } } @@ -604,7 +608,7 @@ GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array) if (!r_dns_servers_array) return -1; - *r_dns_servers_array = NULL; + *r_dns_servers_array = nullptr; char *dns_servers[dns_servers_len]; int dns_servers_count = 0; @@ -648,23 +652,23 @@ GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() { JNIEnv *env = ThreadAndroid::get_env(); ScopedLocalRef<jclass> timeZoneClass(env, env->FindClass("java/util/TimeZone")); - ERR_FAIL_NULL_V(timeZoneClass, NULL); + ERR_FAIL_NULL_V(timeZoneClass, nullptr); jmethodID getDefault = env->GetStaticMethodID(timeZoneClass, "getDefault", "()Ljava/util/TimeZone;"); - ERR_FAIL_NULL_V(getDefault, NULL); + ERR_FAIL_NULL_V(getDefault, nullptr); jmethodID getID = env->GetMethodID(timeZoneClass, "getID", "()Ljava/lang/String;"); - ERR_FAIL_NULL_V(getID, NULL); + ERR_FAIL_NULL_V(getID, nullptr); ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault)); if (!defaultTimeZone) - return NULL; + return nullptr; ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID)); if (!defaultTimeZoneID) - return NULL; + return nullptr; const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0); diff --git a/modules/mono/mono_gd/gd_mono_android.h b/modules/mono/mono_gd/support/android_support.h index 0e04847924..dc2e6c95ed 100644..100755 --- a/modules/mono/mono_gd/gd_mono_android.h +++ b/modules/mono/mono_gd/support/android_support.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gd_mono_android.h */ +/* android_support.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,28 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GD_MONO_ANDROID_H -#define GD_MONO_ANDROID_H +#ifndef ANDROID_SUPPORT_H +#define ANDROID_SUPPORT_H #if defined(ANDROID_ENABLED) #include "core/ustring.h" -namespace GDMonoAndroid { +namespace gdmono { +namespace android { +namespace support { String get_app_native_lib_dir(); void initialize(); +void cleanup(); void register_internal_calls(); -void cleanup(); - -} // namespace GDMonoAndroid +} // namespace support +} // namespace android +} // namespace gdmono #endif // ANDROID_ENABLED -#endif // GD_MONO_ANDROID_H +#endif // ANDROID_SUPPORT_H diff --git a/modules/mono/utils/thread_local.cpp b/modules/mono/mono_gd/support/ios_support.h index 4f10e3fb85..e28af120e3 100644..100755 --- a/modules/mono/utils/thread_local.cpp +++ b/modules/mono/mono_gd/support/ios_support.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* thread_local.cpp */ +/* ios_support.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,80 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "thread_local.h" +#ifndef IOS_SUPPORT_H +#define IOS_SUPPORT_H -#ifdef WINDOWS_ENABLED -#include <windows.h> -#else -#include <pthread.h> -#endif +#if defined(IPHONE_ENABLED) -#include "core/os/memory.h" -#include "core/print_string.h" +#include "core/ustring.h" -struct ThreadLocalStorage::Impl { +namespace gdmono { +namespace ios { +namespace support { -#ifdef WINDOWS_ENABLED - DWORD dwFlsIndex; -#else - pthread_key_t key; -#endif +void initialize(); +void cleanup(); - void *get_value() const { -#ifdef WINDOWS_ENABLED - return FlsGetValue(dwFlsIndex); -#else - return pthread_getspecific(key); -#endif - } +} // namespace support +} // namespace ios +} // namespace gdmono - void set_value(void *p_value) const { -#ifdef WINDOWS_ENABLED - FlsSetValue(dwFlsIndex, p_value); -#else - pthread_setspecific(key, p_value); -#endif - } +#endif // IPHONE_ENABLED -#ifdef WINDOWS_ENABLED -#define _CALLBACK_FUNC_ __stdcall -#else -#define _CALLBACK_FUNC_ -#endif - - Impl(void(_CALLBACK_FUNC_ *p_destr_callback_func)(void *)) { -#ifdef WINDOWS_ENABLED - dwFlsIndex = FlsAlloc(p_destr_callback_func); - ERR_FAIL_COND(dwFlsIndex == FLS_OUT_OF_INDEXES); -#else - pthread_key_create(&key, p_destr_callback_func); -#endif - } - - ~Impl() { -#ifdef WINDOWS_ENABLED - FlsFree(dwFlsIndex); -#else - pthread_key_delete(key); -#endif - } -}; - -void *ThreadLocalStorage::get_value() const { - return pimpl->get_value(); -} - -void ThreadLocalStorage::set_value(void *p_value) const { - pimpl->set_value(p_value); -} - -void ThreadLocalStorage::alloc(void(_CALLBACK_FUNC_ *p_destr_callback)(void *)) { - pimpl = memnew(ThreadLocalStorage::Impl(p_destr_callback)); -} - -#undef _CALLBACK_FUNC_ - -void ThreadLocalStorage::free() { - memdelete(pimpl); - pimpl = NULL; -} +#endif // IOS_SUPPORT_H diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm new file mode 100755 index 0000000000..e3d1a647fd --- /dev/null +++ b/modules/mono/mono_gd/support/ios_support.mm @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* ios_support.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "ios_support.h" + +#if defined(IPHONE_ENABLED) + +#import <Foundation/Foundation.h> +#include <os/log.h> + +#include "core/ustring.h" + +#include "../gd_mono_marshal.h" + +// Implemented mostly following: https://github.com/mono/mono/blob/master/sdks/ios/app/runtime.m + +// Definition generated by the Godot exporter +extern "C" void gd_mono_setup_aot(); + +namespace gdmono { +namespace ios { +namespace support { + +void ios_mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) { + os_log_info(OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message); + if (fatal) { + os_log_info(OS_LOG_DEFAULT, "Exit code: %d.", 1); + exit(1); + } +} + +void initialize() { + mono_dllmap_insert(NULL, "System.Native", NULL, "__Internal", NULL); + mono_dllmap_insert(NULL, "System.IO.Compression.Native", NULL, "__Internal", NULL); + mono_dllmap_insert(NULL, "System.Security.Cryptography.Native.Apple", NULL, "__Internal", NULL); + +#ifdef IOS_DEVICE + // This function is defined in an auto-generated source file + gd_mono_setup_aot(); +#endif + + mono_set_signal_chaining(true); + mono_set_crash_chaining(true); +} + +void cleanup() { +} + +} // namespace support +} // namespace ios +} // namespace gdmono + +// The following are P/Invoke functions required by the monotouch profile of the BCL. +// These are P/Invoke functions and not internal calls, hence why they use +// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'. + +#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default"))) + +GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() { + NSLocale *locale = [NSLocale currentLocale]; + NSString *countryCode = [locale objectForKey:NSLocaleCountryCode]; + if (countryCode == NULL) { + return strdup("US"); + } + return strdup([countryCode UTF8String]); +} + +GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) { + int length = 0; + const uint16_t *ptr = p_unicode_message; + while (*ptr++) + length += sizeof(uint16_t); + NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding]; + + os_log_info(OS_LOG_DEFAULT, "%{public}@", msg); +} + +GD_PINVOKE_EXPORT const char *xamarin_GetFolderPath(int p_folder) { + NSSearchPathDirectory dd = (NSSearchPathDirectory)p_folder; + NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:dd inDomains:NSUserDomainMask] lastObject]; + NSString *path = [url path]; + return strdup([path UTF8String]); +} + +GD_PINVOKE_EXPORT char *xamarin_timezone_get_local_name() { + NSTimeZone *tz = nil; + tz = [NSTimeZone localTimeZone]; + NSString *name = [tz name]; + return (name != nil) ? strdup([name UTF8String]) : strdup("Local"); +} + +GD_PINVOKE_EXPORT char **xamarin_timezone_get_names(uint32_t *p_count) { + NSArray *array = [NSTimeZone knownTimeZoneNames]; + *p_count = array.count; + char **result = (char **)malloc(sizeof(char *) * (*p_count)); + for (uint32_t i = 0; i < *p_count; i++) { + NSString *s = [array objectAtIndex:i]; + result[i] = strdup(s.UTF8String); + } + return result; +} + +GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *p_size) { // FIXME: uint32_t since Dec 2019, unsigned long before + NSTimeZone *tz = nil; + if (p_name) { + NSString *n = [[NSString alloc] initWithUTF8String:p_name]; + tz = [[[NSTimeZone alloc] initWithName:n] autorelease]; + [n release]; + } else { + tz = [NSTimeZone localTimeZone]; + } + NSData *data = [tz data]; + *p_size = [data length]; + void *result = malloc(*p_size); + memcpy(result, data.bytes, *p_size); + return result; +} + +GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) { + // FIXME: What's this for? No idea how to implement. + os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'"); +} + +#endif // IPHONE_ENABLED diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp index 4823ba3679..94431e7c30 100644 --- a/modules/mono/register_types.cpp +++ b/modules/mono/register_types.cpp @@ -34,11 +34,11 @@ #include "csharp_script.h" -CSharpLanguage *script_language_cs = NULL; +CSharpLanguage *script_language_cs = nullptr; Ref<ResourceFormatLoaderCSharpScript> resource_loader_cs; Ref<ResourceFormatSaverCSharpScript> resource_saver_cs; -_GodotSharp *_godotsharp = NULL; +_GodotSharp *_godotsharp = nullptr; void register_mono_types() { ClassDB::register_class<CSharpScript>(); diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 718bc2bb93..e77a2e98f2 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -36,103 +36,192 @@ #include "mono_gd/gd_mono_marshal.h" #include "mono_gd/gd_mono_utils.h" -namespace SignalAwaiterUtils { - -Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p_target, MonoObject *p_awaiter) { - +Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter) { ERR_FAIL_NULL_V(p_source, ERR_INVALID_DATA); ERR_FAIL_NULL_V(p_target, ERR_INVALID_DATA); - Ref<SignalAwaiterHandle> sa_con = memnew(SignalAwaiterHandle(p_awaiter)); -#ifdef DEBUG_ENABLED - sa_con->set_connection_target(p_target); -#endif + // TODO: Use pooling for ManagedCallable instances. + SignalAwaiterCallable *awaiter_callable = memnew(SignalAwaiterCallable(p_target, p_awaiter, p_signal)); + Callable callable = Callable(awaiter_callable); + + return p_source->connect(p_signal, callable, Vector<Variant>(), Object::CONNECT_ONESHOT); +} + +bool SignalAwaiterCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + const SignalAwaiterCallable *a = static_cast<const SignalAwaiterCallable *>(p_a); + const SignalAwaiterCallable *b = static_cast<const SignalAwaiterCallable *>(p_b); - Vector<Variant> binds; - binds.push_back(sa_con); + if (a->target_id != b->target_id) + return false; + + if (a->signal != b->signal) + return false; + + return true; +} + +bool SignalAwaiterCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + if (compare_equal(p_a, p_b)) + return false; + return p_a < p_b; +} + +uint32_t SignalAwaiterCallable::hash() const { + uint32_t hash = signal.hash(); + return hash_djb2_one_64(target_id, hash); +} - Error err = p_source->connect_compat(p_signal, sa_con.ptr(), - CSharpLanguage::get_singleton()->get_string_names()._signal_callback, - binds, Object::CONNECT_ONESHOT); +String SignalAwaiterCallable::get_as_text() const { + Object *base = ObjectDB::get_instance(target_id); + if (base) { + String class_name = base->get_class(); + Ref<Script> script = base->get_script(); + if (script.is_valid() && script->get_path().is_resource_file()) { - if (err != OK) { - // Set it as completed to prevent it from calling the failure callback when released. - // The awaiter will be aware of the failure by checking the returned error. - sa_con->set_completed(true); + class_name += "(" + script->get_path().get_file() + ")"; + } + return class_name + "::SignalAwaiterMiddleman::" + String(signal); + } else { + return "null::SignalAwaiterMiddleman::" + String(signal); } +} - return err; +CallableCustom::CompareEqualFunc SignalAwaiterCallable::get_compare_equal_func() const { + return compare_equal_func_ptr; } -} // namespace SignalAwaiterUtils -Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +CallableCustom::CompareLessFunc SignalAwaiterCallable::get_compare_less_func() const { + return compare_less_func_ptr; +} + +ObjectID SignalAwaiterCallable::get_object() const { + return target_id; +} + +void SignalAwaiterCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better + r_return_value = Variant(); #ifdef DEBUG_ENABLED - ERR_FAIL_COND_V_MSG(conn_target_id.is_valid() && !ObjectDB::get_instance(conn_target_id), Variant(), + ERR_FAIL_COND_MSG(target_id.is_valid() && !ObjectDB::get_instance(target_id), "Resumed after await, but class instance is gone."); #endif - if (p_argcount < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - return Variant(); - } + MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_argcount); - Ref<SignalAwaiterHandle> self = *p_args[p_argcount - 1]; - - if (self.is_null()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = p_argcount - 1; - r_error.expected = Variant::OBJECT; - return Variant(); + for (int i = 0; i < p_argcount; i++) { + MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_arguments[i]); + mono_array_setref(signal_args, i, boxed); } - set_completed(true); - - int signal_argc = p_argcount - 1; - MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), signal_argc); + MonoObject *awaiter = awaiter_handle.get_target(); - for (int i = 0; i < signal_argc; i++) { - MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]); - mono_array_setref(signal_args, i, boxed); + if (!awaiter) { + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; } - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(get_target(), signal_args, &exc); - GD_MONO_END_RUNTIME_INVOKE; + MonoException *exc = nullptr; + CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(awaiter, signal_args, &exc); if (exc) { GDMonoUtils::set_pending_exception(exc); - ERR_FAIL_V(Variant()); + ERR_FAIL(); + } else { + r_call_error.error = Callable::CallError::CALL_OK; } +} - return Variant(); +SignalAwaiterCallable::SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal) : + target_id(p_target->get_instance_id()), + awaiter_handle(MonoGCHandleData::new_strong_handle(p_awaiter)), + signal(p_signal) { } -void SignalAwaiterHandle::_bind_methods() { +SignalAwaiterCallable::~SignalAwaiterCallable() { + awaiter_handle.release(); +} - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &SignalAwaiterHandle::_signal_callback, MethodInfo("_signal_callback")); +bool EventSignalCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + const EventSignalCallable *a = static_cast<const EventSignalCallable *>(p_a); + const EventSignalCallable *b = static_cast<const EventSignalCallable *>(p_b); + + if (a->owner != b->owner) + return false; + + if (a->event_signal != b->event_signal) + return false; + + return true; } -SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) : - MonoGCHandle(MonoGCHandle::new_strong_handle(p_managed), STRONG_HANDLE) {} +bool EventSignalCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + if (compare_equal(p_a, p_b)) + return false; + return p_a < p_b; +} -SignalAwaiterHandle::~SignalAwaiterHandle() { +uint32_t EventSignalCallable::hash() const { + uint32_t hash = event_signal->field->get_name().hash(); + return hash_djb2_one_64(owner->get_instance_id(), hash); +} - if (!completed) { - MonoObject *awaiter = get_target(); +String EventSignalCallable::get_as_text() const { + String class_name = owner->get_class(); + Ref<Script> script = owner->get_script(); + if (script.is_valid() && script->get_path().is_resource_file()) { + class_name += "(" + script->get_path().get_file() + ")"; + } + StringName signal = event_signal->field->get_name(); + return class_name + "::EventSignalMiddleman::" + String(signal); +} - if (awaiter) { - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback).invoke(awaiter, &exc); - GD_MONO_END_RUNTIME_INVOKE; +CallableCustom::CompareEqualFunc EventSignalCallable::get_compare_equal_func() const { + return compare_equal_func_ptr; +} - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL(); - } - } +CallableCustom::CompareLessFunc EventSignalCallable::get_compare_less_func() const { + return compare_less_func_ptr; +} + +ObjectID EventSignalCallable::get_object() const { + return owner->get_instance_id(); +} + +StringName EventSignalCallable::get_signal() const { + return event_signal->field->get_name(); +} + +void EventSignalCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better + r_return_value = Variant(); + + ERR_FAIL_COND(p_argcount < event_signal->invoke_method->get_parameters_count()); + + CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(owner->get_script_instance()); + ERR_FAIL_NULL(csharp_instance); + + MonoObject *owner_managed = csharp_instance->get_mono_object(); + ERR_FAIL_NULL(owner_managed); + + MonoObject *delegate_field_value = event_signal->field->get_value(owner_managed); + if (!delegate_field_value) { + r_call_error.error = Callable::CallError::CALL_OK; + return; } + + MonoException *exc = nullptr; + event_signal->invoke_method->invoke(delegate_field_value, p_arguments, &exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); + ERR_FAIL(); + } else { + r_call_error.error = Callable::CallError::CALL_OK; + } +} + +EventSignalCallable::EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal) : + owner(p_owner), + event_signal(p_event_signal) { } diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h index 012f6e5bb3..c550315a23 100644 --- a/modules/mono/signal_awaiter_utils.h +++ b/modules/mono/signal_awaiter_utils.h @@ -32,40 +32,66 @@ #define SIGNAL_AWAITER_UTILS_H #include "core/reference.h" + +#include "csharp_script.h" #include "mono_gc_handle.h" -namespace SignalAwaiterUtils { +Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter); + +class SignalAwaiterCallable : public CallableCustom { + ObjectID target_id; + MonoGCHandleData awaiter_handle; + StringName signal; + +public: + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); -Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p_target, MonoObject *p_awaiter); -} + static constexpr CompareEqualFunc compare_equal_func_ptr = &SignalAwaiterCallable::compare_equal; + static constexpr CompareEqualFunc compare_less_func_ptr = &SignalAwaiterCallable::compare_less; -class SignalAwaiterHandle : public MonoGCHandle { + uint32_t hash() const override; - GDCLASS(SignalAwaiterHandle, MonoGCHandle); + String get_as_text() const override; - bool completed; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; -#ifdef DEBUG_ENABLED - ObjectID conn_target_id; -#endif + ObjectID get_object() const override; - Variant _signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + _FORCE_INLINE_ StringName get_signal() const { return signal; } + + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + + SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal); + ~SignalAwaiterCallable(); +}; -protected: - static void _bind_methods(); +class EventSignalCallable : public CallableCustom { + Object *owner; + const CSharpScript::EventSignal *event_signal; public: - _FORCE_INLINE_ bool is_completed() { return completed; } - _FORCE_INLINE_ void set_completed(bool p_completed) { completed = p_completed; } + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); + + static constexpr CompareEqualFunc compare_equal_func_ptr = &EventSignalCallable::compare_equal; + static constexpr CompareEqualFunc compare_less_func_ptr = &EventSignalCallable::compare_less; + + uint32_t hash() const override; + + String get_as_text() const override; + + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + + ObjectID get_object() const override; + + StringName get_signal() const; -#ifdef DEBUG_ENABLED - _FORCE_INLINE_ void set_connection_target(Object *p_target) { - conn_target_id = p_target->get_instance_id(); - } -#endif + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; - SignalAwaiterHandle(MonoObject *p_managed); - ~SignalAwaiterHandle(); + EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal); }; #endif // SIGNAL_AWAITER_UTILS_H diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h index 754000dc14..8650d6cc09 100644 --- a/modules/mono/utils/macros.h +++ b/modules/mono/utils/macros.h @@ -36,38 +36,6 @@ #define _GD_VARNAME_CONCAT_(m_a, m_b, m_c) _GD_VARNAME_CONCAT_A_(m_a, m_b, m_c) #define GD_UNIQUE_NAME(m_name) _GD_VARNAME_CONCAT_(m_name, _, __COUNTER__) -// static assert -// TODO: Get rid of this macro once we upgrade to C++11 - -#ifdef __cpp_static_assert -#define GD_STATIC_ASSERT(m_cond) static_assert((m_cond), "Condition '" #m_cond "' failed") -#else -#define GD_STATIC_ASSERT(m_cond) typedef int GD_UNIQUE_NAME(godot_static_assert)[((m_cond) ? 1 : -1)] -#endif - -// final -// TODO: Get rid of this macro once we upgrade to C++11 - -#if (__cplusplus >= 201103L) -#define GD_FINAL final -#else -#define GD_FINAL -#endif - -// noreturn -// TODO: Get rid of this macro once we upgrade to C++11 - -#if (__cplusplus >= 201103L) -#define GD_NORETURN [[noreturn]] -#elif defined(__GNUC__) -#define GD_NORETURN __attribute__((noreturn)) -#elif defined(_MSC_VER) -#define GD_NORETURN __declspec(noreturn) -#else -#define GD_NORETURN -#pragma message "Macro GD_NORETURN will have no effect" -#endif - // unreachable #if defined(_MSC_VER) @@ -81,4 +49,25 @@ } while (true); #endif +namespace gdmono { + +template <typename F> +struct ScopeExit { + ScopeExit(F p_exit_func) : + exit_func(p_exit_func) {} + ~ScopeExit() { exit_func(); } + F exit_func; +}; + +class ScopeExitAux { +public: + template <typename F> + ScopeExit<F> operator+(F p_exit_func) { return ScopeExit<F>(p_exit_func); } +}; + +} // namespace gdmono + +#define SCOPE_EXIT \ + auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() + #endif // UTIL_MACROS_H diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp index c1cd5f1db4..8f0ad8ba5e 100644 --- a/modules/mono/utils/mono_reg_utils.cpp +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -73,13 +73,13 @@ LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) buffer.resize(512); DWORD dwBufferSize = buffer.size(); - LONG res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, NULL, (LPBYTE)buffer.ptr(), &dwBufferSize); + LONG res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); if (res == ERROR_MORE_DATA) { // dwBufferSize now contains the actual size Vector<WCHAR> buffer; buffer.resize(dwBufferSize); - res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, NULL, (LPBYTE)buffer.ptr(), &dwBufferSize); + res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); } if (res == ERROR_SUCCESS) { @@ -180,7 +180,7 @@ String find_msbuild_tools_path() { String output; int exit_code; - OS::get_singleton()->execute(vswhere_path, vswhere_args, true, NULL, &output, &exit_code); + OS::get_singleton()->execute(vswhere_path, vswhere_args, true, nullptr, &output, &exit_code); if (exit_code == 0) { Vector<String> lines = output.split("\n"); diff --git a/modules/mono/utils/osx_utils.cpp b/modules/mono/utils/osx_utils.cpp index 432b306414..8fadf3c109 100644 --- a/modules/mono/utils/osx_utils.cpp +++ b/modules/mono/utils/osx_utils.cpp @@ -39,9 +39,9 @@ bool osx_is_app_bundle_installed(const String &p_bundle_id) { - CFURLRef app_url = NULL; - CFStringRef bundle_id = CFStringCreateWithCString(NULL, p_bundle_id.utf8(), kCFStringEncodingUTF8); - OSStatus result = LSFindApplicationForInfo(kLSUnknownCreator, bundle_id, NULL, NULL, &app_url); + CFURLRef app_url = nullptr; + CFStringRef bundle_id = CFStringCreateWithCString(nullptr, p_bundle_id.utf8(), kCFStringEncodingUTF8); + OSStatus result = LSFindApplicationForInfo(kLSUnknownCreator, bundle_id, nullptr, nullptr, &app_url); CFRelease(bundle_id); if (app_url) diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index 545da6c79e..973375a471 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -80,7 +80,7 @@ String find_executable(const String &p_name) { String cwd() { #ifdef WINDOWS_ENABLED - const DWORD expected_size = ::GetCurrentDirectoryW(0, NULL); + const DWORD expected_size = ::GetCurrentDirectoryW(0, nullptr); String buffer; buffer.resize((int)expected_size); @@ -90,7 +90,7 @@ String cwd() { return buffer.simplify_path(); #else char buffer[PATH_MAX]; - if (::getcwd(buffer, sizeof(buffer)) == NULL) + if (::getcwd(buffer, sizeof(buffer)) == nullptr) return "."; String result; @@ -114,12 +114,12 @@ String realpath(const String &p_path) { // 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); + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) return p_path; - const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, NULL, 0, FILE_NAME_NORMALIZED); + const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, nullptr, 0, FILE_NAME_NORMALIZED); if (expected_size == 0) { ::CloseHandle(hFile); @@ -133,7 +133,7 @@ String realpath(const String &p_path) { ::CloseHandle(hFile); return buffer.simplify_path(); #elif UNIX_ENABLED - char *resolved_path = ::realpath(p_path.utf8().get_data(), NULL); + char *resolved_path = ::realpath(p_path.utf8().get_data(), nullptr); if (!resolved_path) return p_path; diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 49c4fb3f73..907811355f 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -212,7 +212,7 @@ String str_format(const char *p_format, ...) { #define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy) #else #define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf(m_buffer, m_count, m_format, m_args_copy) -#define gd_vscprintf(m_format, m_args_copy) vsnprintf(NULL, 0, p_format, m_args_copy) +#define gd_vscprintf(m_format, m_args_copy) vsnprintf(nullptr, 0, p_format, m_args_copy) #endif String str_format(const char *p_format, va_list p_list) { diff --git a/modules/mono/utils/thread_local.h b/modules/mono/utils/thread_local.h deleted file mode 100644 index b1cc2e37ea..0000000000 --- a/modules/mono/utils/thread_local.h +++ /dev/null @@ -1,177 +0,0 @@ -/*************************************************************************/ -/* thread_local.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef THREAD_LOCAL_H -#define THREAD_LOCAL_H - -#ifdef HAVE_CXX11_THREAD_LOCAL -#define _THREAD_LOCAL_(m_t) thread_local m_t -#else - -#if !defined(__GNUC__) && !defined(_MSC_VER) -#error Platform or compiler not supported -#endif - -#if defined(__GNUC__) - -#ifdef HAVE_GCC___THREAD -#define _THREAD_LOCAL_(m_t) __thread m_t -#else -#define USE_CUSTOM_THREAD_LOCAL -#endif - -#elif defined(_MSC_VER) - -#ifdef HAVE_DECLSPEC_THREAD -#define _THREAD_LOCAL_(m_t) __declspec(thread) m_t -#else -#define USE_CUSTOM_THREAD_LOCAL -#endif - -#endif // __GNUC__ _MSC_VER - -#endif // HAVE_CXX11_THREAD_LOCAL - -#ifdef USE_CUSTOM_THREAD_LOCAL -#define _THREAD_LOCAL_(m_t) ThreadLocal<m_t> -#endif - -#include "core/typedefs.h" - -#ifdef WINDOWS_ENABLED -#define _CALLBACK_FUNC_ __stdcall -#else -#define _CALLBACK_FUNC_ -#endif - -struct ThreadLocalStorage { - - void *get_value() const; - void set_value(void *p_value) const; - - void alloc(void(_CALLBACK_FUNC_ *p_destr_callback)(void *)); - void free(); - -private: - struct Impl; - Impl *pimpl; -}; - -template <typename T> -class ThreadLocal { - - ThreadLocalStorage storage; - - T init_val; - - static void _CALLBACK_FUNC_ destr_callback(void *tls_data) { - memdelete(static_cast<T *>(tls_data)); - } - - T *_tls_get_value() const { - void *tls_data = storage.get_value(); - - if (tls_data) - return static_cast<T *>(tls_data); - - T *data = memnew(T(init_val)); - - storage.set_value(data); - - return data; - } - - void _initialize(const T &p_init_val) { - init_val = p_init_val; - storage.alloc(&destr_callback); - } - -public: - ThreadLocal() { - _initialize(T()); - } - - ThreadLocal(const T &p_init_val) { - _initialize(p_init_val); - } - - ThreadLocal(const ThreadLocal &other) { - _initialize(*other._tls_get_value()); - } - - ~ThreadLocal() { - storage.free(); - } - - _FORCE_INLINE_ T *operator&() const { - return _tls_get_value(); - } - - _FORCE_INLINE_ operator T &() const { - return *_tls_get_value(); - } - - _FORCE_INLINE_ ThreadLocal &operator=(const T &val) { - T *ptr = _tls_get_value(); - *ptr = val; - return *this; - } -}; - -struct FlagScopeGuard { - - FlagScopeGuard(bool &p_flag) : - flag(p_flag) { - flag = !flag; - } - - ~FlagScopeGuard() { - flag = !flag; - } - -private: - bool &flag; -}; - -#undef _CALLBACK_FUNC_ - -#define _TLS_RECURSION_GUARD_V_(m_ret) \ - static _THREAD_LOCAL_(bool) _recursion_flag_ = false; \ - if (_recursion_flag_) \ - return m_ret; \ - FlagScopeGuard _recursion_guard_(_recursion_flag_); - -#define _TLS_RECURSION_GUARD_ \ - static _THREAD_LOCAL_(bool) _recursion_flag_ = false; \ - if (_recursion_flag_) \ - return; \ - FlagScopeGuard _recursion_guard_(_recursion_flag_); - -#endif // THREAD_LOCAL_H |