diff options
Diffstat (limited to 'modules/mono')
246 files changed, 21492 insertions, 18829 deletions
diff --git a/modules/mono/.editorconfig b/modules/mono/.editorconfig index c9dcd7724e..9434d0693c 100644 --- a/modules/mono/.editorconfig +++ b/modules/mono/.editorconfig @@ -12,3 +12,32 @@ insert_final_newline = true trim_trailing_whitespace = true max_line_length = 120 csharp_indent_case_contents_when_block = false + +[*.cs] +# CA1707: Identifiers should not contain underscores +# TODO: +# Maybe we could disable this selectively only +# where it's not desired and for generated code. +dotnet_diagnostic.CA1707.severity = none +# CA1711: Identifiers should not have incorrect suffix +# Disable warning for suffixes like EventHandler, Flags, Enum, etc. +dotnet_diagnostic.CA1711.severity = none +# CA1716: Identifiers should not match keywords +# TODO: We should look into this. +dotnet_diagnostic.CA1716.severity = warning +# CA1720: Identifiers should not contain type names +dotnet_diagnostic.CA1720.severity = none +# CA1805: Do not initialize unnecessarily +# Don't tell me what to do. +dotnet_diagnostic.CA1805.severity = none +# CA1304: Specify CultureInfo +# TODO: We should look into this. +dotnet_diagnostic.CA1304.severity = warning +# CA1305: Specify IFormatProvider +# TODO: We should look into this. Disabled for now because it's annoying. +dotnet_diagnostic.CA1305.severity = none +# CA1310: Specify StringComparison for correctness +# TODO: We should look into this. Disabled for now because it's annoying. +dotnet_diagnostic.CA1310.severity = none +# Diagnostics to prevent defensive copies of `in` struct parameters +resharper_possibly_impure_method_call_on_readonly_variable_highlighting = error diff --git a/modules/mono/Directory.Build.props b/modules/mono/Directory.Build.props index fbf864b11b..f7c8a825f9 100644 --- a/modules/mono/Directory.Build.props +++ b/modules/mono/Directory.Build.props @@ -1,3 +1,6 @@ <Project> - <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" /> + <PropertyGroup> + <GodotSdkPackageVersionsFilePath>$(MSBuildThisFileDirectory)\SdkPackageVersions.props</GodotSdkPackageVersionsFilePath> + </PropertyGroup> + <Import Project="$(GodotSdkPackageVersionsFilePath)" /> </Project> diff --git a/modules/mono/Directory.Build.targets b/modules/mono/Directory.Build.targets new file mode 100644 index 0000000000..98410b93ae --- /dev/null +++ b/modules/mono/Directory.Build.targets @@ -0,0 +1,22 @@ +<Project> + <PropertyGroup> + <_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' And '$(PackageId)' != '' And '$(GeneratePackageOnBuild.ToLower())' == 'true' ">true</_HasNuGetPackage> + <_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' ">false</_HasNuGetPackage> + </PropertyGroup> + <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack" + Condition=" '$(_HasNuGetPackage)' == 'true' "> + <PropertyGroup> + <GodotSourceRootPath>$(MSBuildThisFileDirectory)\..\..\</GodotSourceRootPath> + <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> + </PropertyGroup> + <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> + </Target> + <Target Name="PushNuGetPackagesToLocalSource" BeforeTargets="Pack" + Condition=" '$(_HasNuGetPackage)' == 'true' And '$(PushNuGetToLocalSource)' != '' "> + <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(PushNuGetToLocalSource)\" /> + </Target> + <Target Name="ClearNuGetLocalPackageCache" BeforeTargets="Pack" + Condition=" '$(_HasNuGetPackage)' == 'true' And '$(ClearNuGetLocalCache.ToLower())' == 'true' "> + <RemoveDir Directories="$(NugetPackageRoot)/$(PackageId.ToLower())/$(PackageVersion)"/> + </Target> +</Project> diff --git a/modules/mono/README.md b/modules/mono/README.md new file mode 100644 index 0000000000..ebbc6b0f80 --- /dev/null +++ b/modules/mono/README.md @@ -0,0 +1,45 @@ +# How to build and run + +1. Build Godot with the module enabled: `module_mono_enabled=yes`. +2. After building Godot, use it to generate the C# glue code: + ```sh + <godot_binary> --generate-mono-glue ./modules/mono/glue + ``` +3. Build the C# solutions: + ```sh + ./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin + ``` + +The paths specified in these examples assume the command is being run from +the Godot source root. + +# How to deal with NuGet packages + +We distribute the API assemblies, our source generators, and our custom +MSBuild project SDK as NuGet packages. This is all transparent to the user, +but it can make things complicated during development. + +In order to use Godot with a development of those packages, we must create +a local NuGet source where MSBuild can find them. This can be done with +the .NET CLI: + +```sh +dotnet nuget add source ~/MyLocalNugetSource --name MyLocalNugetSource +``` + +The Godot NuGet packages must be added to that local source. Additionally, +we must make sure there are no other versions of the package in the NuGet +cache, as MSBuild may pick one of those instead. + +In order to simplify this process, the `build_assemblies.py` script provides +the following `--push-nupkgs-local` option: + +```sh +./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin \ + --push-nupkgs-local ~/MyLocalNugetSource +``` + +This option ensures the packages will be added to the specified local NuGet +source and that conflicting versions of the package are removed from the +NuGet cache. It's recommended to always use this option when building the +C# solutions during development to avoid mistakes. diff --git a/modules/mono/SCsub b/modules/mono/SCsub index d10ebc7b47..7764ba0b45 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -7,49 +7,14 @@ Import("env_modules") env_mono = env_modules.Clone() -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") - -# Glue sources -if env_mono["mono_glue"]: - env_mono.Append(CPPDEFINES=["MONO_GLUE_ENABLED"]) - - import os.path - - if not os.path.isfile("glue/mono_glue.gen.cpp"): - raise RuntimeError("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 Mono mono_configure.configure(env, env_mono) -if env_mono["tools"] and env_mono["mono_glue"] and env_mono["build_cil"]: - # Build Godot API solution - import build_scripts.api_solution_build as api_solution_build - - 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) - - # Build Godot.NET.Sdk - import build_scripts.godot_net_sdk_build as godot_net_sdk_build - - godot_net_sdk_build.build(env_mono) - # 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, "glue/mono_glue.gen.cpp") env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp") env_mono.add_source_files(env.modules_sources, "utils/*.cpp") diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props index bdec051625..65094aa34f 100644 --- a/modules/mono/SdkPackageVersions.props +++ b/modules/mono/SdkPackageVersions.props @@ -1,7 +1,8 @@ <Project> <PropertyGroup> <PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot> - <PackageVersion_Godot_NET_Sdk>4.0.0-dev6</PackageVersion_Godot_NET_Sdk> - <PackageVersion_Godot_SourceGenerators>4.0.0-dev3</PackageVersion_Godot_SourceGenerators> + <PackageVersion_GodotSharp>4.0.0-dev</PackageVersion_GodotSharp> + <PackageVersion_Godot_NET_Sdk>4.0.0-dev8</PackageVersion_Godot_NET_Sdk> + <PackageVersion_Godot_SourceGenerators>4.0.0-dev8</PackageVersion_Godot_SourceGenerators> </PropertyGroup> </Project> diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py deleted file mode 100644 index 9abac22df6..0000000000 --- a/modules/mono/build_scripts/api_solution_build.py +++ /dev/null @@ -1,80 +0,0 @@ -# Build the Godot API solution - -import os - -from SCons.Script import Dir - - -def build_api_solution(source, target, env): - # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - - module_dir = env["module_dir"] - - solution_path = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") - - build_config = env["solution_build_config"] - - extra_msbuild_args = ["/p:NoWarn=1591"] # Ignore missing documentation warnings - - from .solution_builder import build_solution - - build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args) - - # Copy targets - - core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharp", "bin", build_config)) - editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharpEditor", "bin", build_config)) - - dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) - - if not os.path.isdir(dst_dir): - assert not os.path.isfile(dst_dir) - os.makedirs(dst_dir) - - def copy_target(target_path): - from shutil import copy - - filename = os.path.basename(target_path) - - src_path = os.path.join(core_src_dir, filename) - if not os.path.isfile(src_path): - src_path = os.path.join(editor_src_dir, filename) - - copy(src_path, target_path) - - for scons_target in target: - copy_target(str(scons_target)) - - -def build(env_mono): - assert env_mono["tools"] - - target_filenames = [ - "GodotSharp.dll", - "GodotSharp.pdb", - "GodotSharp.xml", - "GodotSharpEditor.dll", - "GodotSharpEditor.pdb", - "GodotSharpEditor.xml", - ] - - 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) - - 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 - ) - env_mono.AlwaysBuild(cmd) - - # Make the Release build of the API solution depend on the Debug build. - # We do this in order to prevent SCons from building them in parallel, - # which can freak out MSBuild. In many cases, one of the builds would - # hang indefinitely requiring a key to be pressed for it to continue. - depend_cmd = cmd - - return depend_cmd diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py new file mode 100755 index 0000000000..fa3be684bd --- /dev/null +++ b/modules/mono/build_scripts/build_assemblies.py @@ -0,0 +1,329 @@ +#!/usr/bin/python3 + +import os +import os.path +import shlex +import subprocess +from dataclasses import dataclass + + +def find_dotnet_cli(): + if os.name == "nt": + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "dotnet") + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): + return hint_path + ".exe" + else: + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "dotnet") + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + +def find_msbuild_standalone_windows(): + msbuild_tools_path = find_msbuild_tools_path_reg() + + if msbuild_tools_path: + return os.path.join(msbuild_tools_path, "MSBuild.exe") + + return None + + +def find_msbuild_mono_windows(mono_prefix): + assert mono_prefix is not None + + mono_bin_dir = os.path.join(mono_prefix, "bin") + msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat") + + if os.path.isfile(msbuild_mono): + return msbuild_mono + + return None + + +def find_msbuild_mono_unix(): + import sys + + hint_dirs = [] + if sys.platform == "darwin": + hint_dirs[:0] = [ + "/Library/Frameworks/Mono.framework/Versions/Current/bin", + "/usr/local/var/homebrew/linked/mono/bin", + ] + + for hint_dir in hint_dirs: + hint_path = os.path.join(hint_dir, "msbuild") + if os.path.isfile(hint_path): + return hint_path + elif os.path.isfile(hint_path + ".exe"): + return hint_path + ".exe" + + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "msbuild") + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): + return hint_path + ".exe" + + return None + + +def find_msbuild_tools_path_reg(): + import subprocess + + program_files = os.getenv("PROGRAMFILES(X86)") + if not program_files: + program_files = os.getenv("PROGRAMFILES") + vswhere = os.path.join(program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe") + + vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"] + + try: + lines = subprocess.check_output([vswhere] + vswhere_args).splitlines() + + for line in lines: + parts = line.decode("utf-8").split(":", 1) + + if len(parts) < 2 or parts[0] != "installationPath": + continue + + val = parts[1].strip() + + if not val: + 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") + if os.path.isdir(msbuild_dir): + return msbuild_dir + + # Directory name "15.0" is used in VS 2017 + return os.path.join(val, "MSBuild", "15.0", "Bin") + + raise ValueError("Cannot find `installationPath` entry") + except ValueError as e: + print("Error reading output from vswhere: " + str(e)) + except OSError: + pass # Fine, vswhere not found + except (subprocess.CalledProcessError, OSError): + pass + + +@dataclass +class ToolsLocation: + dotnet_cli: str = "" + msbuild_standalone: str = "" + msbuild_mono: str = "" + mono_bin_dir: str = "" + + +def find_any_msbuild_tool(mono_prefix): + # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild + + # Find dotnet CLI + dotnet_cli = find_dotnet_cli() + if dotnet_cli: + return ToolsLocation(dotnet_cli=dotnet_cli) + + # Find standalone MSBuild + if os.name == "nt": + msbuild_standalone = find_msbuild_standalone_windows() + if msbuild_standalone: + return ToolsLocation(msbuild_standalone=msbuild_standalone) + + if mono_prefix: + # Find Mono's MSBuild + if os.name == "nt": + msbuild_mono = find_msbuild_mono_windows(mono_prefix) + if msbuild_mono: + return ToolsLocation(msbuild_mono=msbuild_mono) + else: + msbuild_mono = find_msbuild_mono_unix() + if msbuild_mono: + return ToolsLocation(msbuild_mono=msbuild_mono) + + return None + + +def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None): + if msbuild_args is None: + msbuild_args = [] + + using_msbuild_mono = False + + # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild + if tools.dotnet_cli: + args = [tools.dotnet_cli, "msbuild"] + elif tools.msbuild_standalone: + args = [tools.msbuild_standalone] + elif tools.msbuild_mono: + args = [tools.msbuild_mono] + using_msbuild_mono = True + else: + raise RuntimeError("Path to MSBuild or dotnet CLI not provided.") + + args += [sln] + + if len(msbuild_args) > 0: + args += msbuild_args + + print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True) + + msbuild_env = os.environ.copy() + + # Needed when running from Developer Command Prompt for VS + if "PLATFORM" in msbuild_env: + del msbuild_env["PLATFORM"] + + if using_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. + msbuild_env.update( + { + "CscToolExe": os.path.join(tools.mono_bin_dir, "csc.bat"), + "VbcToolExe": os.path.join(tools.mono_bin_dir, "vbc.bat"), + "FscToolExe": os.path.join(tools.mono_bin_dir, "fsharpc.bat"), + } + ) + + return subprocess.call(args, env=msbuild_env) + + +def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local): + target_filenames = [ + "GodotSharp.dll", + "GodotSharp.pdb", + "GodotSharp.xml", + "GodotSharpEditor.dll", + "GodotSharpEditor.pdb", + "GodotSharpEditor.xml", + "GodotPlugins.dll", + "GodotPlugins.pdb", + "GodotPlugins.runtimeconfig.json", + ] + + for build_config in ["Debug", "Release"]: + 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] + + args = ["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"] + if push_nupkgs_local: + args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + + sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") + exit_code = run_msbuild( + msbuild_tool, + sln=sln, + msbuild_args=args, + ) + if exit_code != 0: + return exit_code + + # Copy targets + + core_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharp", "bin", build_config)) + editor_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharpEditor", "bin", build_config)) + plugins_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotPlugins", "bin", build_config, "net6.0")) + + if not os.path.isdir(editor_api_dir): + assert not os.path.isfile(editor_api_dir) + os.makedirs(editor_api_dir) + + def copy_target(target_path): + from shutil import copy + + filename = os.path.basename(target_path) + + src_path = os.path.join(core_src_dir, filename) + if not os.path.isfile(src_path): + src_path = os.path.join(editor_src_dir, filename) + if not os.path.isfile(src_path): + src_path = os.path.join(plugins_src_dir, filename) + + print(f"Copying assembly to {target_path}...") + copy(src_path, target_path) + + for scons_target in targets: + copy_target(scons_target) + + return 0 + + +def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local): + # Godot API + exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local) + if exit_code != 0: + return exit_code + + # GodotTools + sln = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln") + args = ["/restore", "/t:Build", "/p:Configuration=" + ("Debug" if dev_debug else "Release")] + ( + ["/p:GodotPlatform=" + godot_platform] if godot_platform else [] + ) + if push_nupkgs_local: + args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args) + if exit_code != 0: + return exit_code + + # Godot.NET.Sdk + args = ["/restore", "/t:Build", "/p:Configuration=Release"] + if push_nupkgs_local: + args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") + exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args) + if exit_code != 0: + return exit_code + + return 0 + + +def main(): + import argparse + import sys + + parser = argparse.ArgumentParser(description="Builds all Godot .NET solutions") + parser.add_argument("--godot-output-dir", type=str, required=True) + parser.add_argument( + "--dev-debug", + action="store_true", + default=False, + help="Build GodotTools and Godot.NET.Sdk with 'Configuration=Debug'", + ) + parser.add_argument("--godot-platform", type=str, default="") + parser.add_argument("--mono-prefix", type=str, default="") + parser.add_argument("--push-nupkgs-local", type=str, default="") + + args = parser.parse_args() + + this_script_dir = os.path.dirname(os.path.realpath(__file__)) + module_dir = os.path.abspath(os.path.join(this_script_dir, os.pardir)) + + output_dir = os.path.abspath(args.godot_output_dir) + + msbuild_tool = find_any_msbuild_tool(args.mono_prefix) + + if msbuild_tool is None: + print("Unable to find MSBuild") + sys.exit(1) + + exit_code = build_all( + msbuild_tool, + module_dir, + output_dir, + args.godot_platform, + args.dev_debug, + args.push_nupkgs_local, + ) + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/modules/mono/build_scripts/gen_cs_glue_version.py b/modules/mono/build_scripts/gen_cs_glue_version.py deleted file mode 100644 index 98bbb4d9be..0000000000 --- a/modules/mono/build_scripts/gen_cs_glue_version.py +++ /dev/null @@ -1,20 +0,0 @@ -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")] - 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 - - 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_net_sdk_build.py b/modules/mono/build_scripts/godot_net_sdk_build.py deleted file mode 100644 index 8c5a60d2db..0000000000 --- a/modules/mono/build_scripts/godot_net_sdk_build.py +++ /dev/null @@ -1,55 +0,0 @@ -# Build Godot.NET.Sdk solution - -import os - -from SCons.Script import Dir - - -def build_godot_net_sdk(source, target, env): - # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - - module_dir = env["module_dir"] - - solution_path = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") - build_config = "Release" - - from .solution_builder import build_solution - - extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]] - - build_solution(env, solution_path, build_config, extra_msbuild_args) - # No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them. - - -def get_nupkgs_versions(props_file): - import xml.etree.ElementTree as ET - - tree = ET.parse(props_file) - root = tree.getroot() - - return { - "Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(), - "Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(), - } - - -def build(env_mono): - assert env_mono["tools"] - - output_dir = Dir("#bin").abspath - editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools") - nupkgs_dir = os.path.join(editor_tools_dir, "nupkgs") - - module_dir = os.getcwd() - - nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props")) - - target_filenames = [ - "Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"], - "Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"], - ] - - targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames] - - cmd = env_mono.CommandNoCache(targets, [], build_godot_net_sdk, module_dir=module_dir) - env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py deleted file mode 100644 index 3bbbf29d3b..0000000000 --- a/modules/mono/build_scripts/godot_tools_build.py +++ /dev/null @@ -1,38 +0,0 @@ -# Build GodotTools solution - -import os - -from SCons.Script import Dir - - -def build_godot_tools(source, target, env): - # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - - module_dir = env["module_dir"] - - solution_path = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln") - build_config = "Debug" if env["target"] == "debug" else "Release" - - from .solution_builder import build_solution - - extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]] - - build_solution(env, solution_path, build_config, extra_msbuild_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"] - - output_dir = Dir("#bin").abspath - editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools") - - target_filenames = ["GodotTools.dll"] - - if env_mono["target"] == "debug": - target_filenames += ["GodotTools.pdb"] - - targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] - - cmd = env_mono.CommandNoCache(targets, api_sln_cmd, build_godot_tools, module_dir=os.getcwd()) - env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py deleted file mode 100644 index 3459244bc2..0000000000 --- a/modules/mono/build_scripts/make_android_mono_config.py +++ /dev/null @@ -1,55 +0,0 @@ -def generate_compressed_config(config_src, output_dir): - import os.path - - # Source file - with open(os.path.join(output_dir, "android_mono_config.gen.cpp"), "w") as cpp: - with open(config_src, "rb") as f: - buf = f.read() - decompr_size = len(buf) - import zlib - - # Use maximum zlib compression level to further reduce file size - # (at the cost of initial build times). - buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION) - compr_size = len(buf) - - bytes_seq_str = "" - for i, buf_idx in enumerate(range(compr_size)): - if i > 0: - bytes_seq_str += ", " - bytes_seq_str += str(buf[buf_idx]) - - cpp.write( - """/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "android_mono_config.h" - -#ifdef ANDROID_ENABLED - -#include "core/io/compression.h" - - -namespace { - -// config -static const int config_compressed_size = %d; -static const int config_uncompressed_size = %d; -static const unsigned char config_compressed_data[] = { %s }; -} // namespace - -String get_godot_android_mono_config() { - Vector<uint8_t> data; - data.resize(config_uncompressed_size); - uint8_t* w = data.ptrw(); - Compression::decompress(w, config_uncompressed_size, config_compressed_data, - config_compressed_size, Compression::MODE_DEFLATE); - String s; - if (s.parse_utf8((const char *)w, data.size()) != OK) { - ERR_FAIL_V(String()); - } - return s; -} - -#endif // ANDROID_ENABLED -""" - % (compr_size, decompr_size, bytes_seq_str) - ) diff --git a/modules/mono/build_scripts/mono_android_config.xml b/modules/mono/build_scripts/mono_android_config.xml deleted file mode 100644 index e79670afd2..0000000000 --- a/modules/mono/build_scripts/mono_android_config.xml +++ /dev/null @@ -1,28 +0,0 @@ -<configuration> - <dllmap wordsize="32" dll="i:cygwin1.dll" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="i:cygwin1.dll" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="libc" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="libc" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="intl" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="intl" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="libintl" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="libintl" target="/system/lib64/libc.so" /> - <dllmap dll="MonoPosixHelper" target="libMonoPosixHelper.so" /> - <dllmap dll="System.Native" target="libmono-native.so" /> - <dllmap wordsize="32" dll="i:msvcrt" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="i:msvcrt" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="i:msvcrt.dll" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="i:msvcrt.dll" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="sqlite" target="/system/lib/libsqlite.so" /> - <dllmap wordsize="64" dll="sqlite" target="/system/lib64/libsqlite.so" /> - <dllmap wordsize="32" dll="sqlite3" target="/system/lib/libsqlite.so" /> - <dllmap wordsize="64" dll="sqlite3" target="/system/lib64/libsqlite.so" /> - <dllmap wordsize="32" dll="liblog" target="/system/lib/liblog.so" /> - <dllmap wordsize="64" dll="liblog" target="/system/lib64/liblog.so" /> - <dllmap dll="i:kernel32.dll"> - <dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/> - <dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/> - <dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/> - <dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/> - </dllmap> -</configuration> diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index e69904c54b..8141bc2422 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -1,574 +1,307 @@ import os import os.path -import subprocess - -from SCons.Script import Dir, Environment - -if os.name == "nt": - from . import mono_reg_utils as monoreg - - -android_arch_dirs = { - "armv7": "armeabi-v7a", - "arm64v8": "arm64-v8a", - "x86": "x86", - "x86_64": "x86_64", -} - - -def get_android_out_dir(env): - return os.path.join( - Dir("#platform/android/java/lib/libs").abspath, - "release" if env["target"] == "release" else "debug", - android_arch_dirs[env["android_arch"]], - ) - - -def find_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, src_name) - dst_dir = Dir(dst_dir).abspath - - if not os.path.isdir(dst_dir): - os.makedirs(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", "macos", "linuxbsd", "server", "uwp", "haiku"] + return platform in ["windows", "macos", "linuxbsd", "uwp", "haiku"] def is_unix_like(platform): - return platform in ["macos", "linuxbsd", "server", "android", "haiku", "ios"] + return platform in ["macos", "linuxbsd", "android", "haiku", "ios"] def module_supports_tools_on(platform): - return platform not in ["android", "javascript", "ios"] - - -def find_wasm_src_dir(mono_root): - hint_dirs = [ - 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")): - return hint_dir - return "" + return is_desktop(platform) def configure(env, env_mono): - bits = env["bits"] - is_android = env["platform"] == "android" - is_javascript = env["platform"] == "javascript" - is_ios = env["platform"] == "ios" - is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"] + # is_android = env["platform"] == "android" + # is_javascript = env["platform"] == "javascript" + # is_ios = env["platform"] == "ios" + # is_ios_sim = is_ios and env["arch"] in ["x86_32", "x86_64"] tools_enabled = env["tools"] - mono_static = env["mono_static"] - copy_mono_root = env["copy_mono_root"] - - mono_prefix = env["mono_prefix"] - mono_bcl = env["mono_bcl"] - - mono_lib_names = ["mono-2.0-sgen", "monosgen-2.0"] - - 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"]): - # 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") - if is_android and mono_static: - # 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"] - - # Static AOT is only supported on the root domain - mono_single_appdomain = mono_aot_static - - if mono_single_appdomain: - env_mono.Append(CPPDEFINES=["GD_MONO_SINGLE_APPDOMAIN"]) - - if (env["tools"] or env["target"] != "release") and not mono_single_appdomain: + if env["tools"]: env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) - if env["platform"] == "windows": - mono_root = mono_prefix - - if not mono_root and os.name == "nt": - mono_root = monoreg.find_mono_root_dir(bits) - - if not mono_root: - raise RuntimeError( - "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter" - ) + app_host_dir = find_dotnet_app_host_dir(env) - print("Found Mono root directory: " + mono_root) + def check_app_host_file_exists(file): + file_path = os.path.join(app_host_dir, file) + if not os.path.isfile(file_path): + raise RuntimeError("File not found: " + file_path) - mono_lib_path = os.path.join(mono_root, "lib") + # TODO: + # All libnethost does for us is provide a function to find hostfxr. + # If we could handle that logic ourselves we could void linking it. - env.Append(LIBPATH=mono_lib_path) - env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) + # nethost file names: + # static: libnethost.a/lib + # shared: libnethost.a/dylib and nethost.dll + check_app_host_file_exists("libnethost.lib" if os.name == "nt" else "libnethost.a") + check_app_host_file_exists("nethost.h") + check_app_host_file_exists("hostfxr.h") + check_app_host_file_exists("coreclr_delegates.h") - lib_suffixes = [".lib"] + env_mono.Prepend(CPPPATH=app_host_dir) - if not env.msvc: - # MingW supports both '.a' and '.lib' - lib_suffixes.insert(0, ".a") + env.Append(LIBPATH=[app_host_dir]) - if mono_static: - if env.msvc: - mono_static_lib_name = "libmono-static-sgen" - else: - mono_static_lib_name = "libmonosgen-2.0" + # Only the editor build links nethost, which is needed to find hostfxr. + # Exported games don't need this logic as hostfxr is bundled with them. + if tools_enabled: + libnethost_path = os.path.join(app_host_dir, "libnethost.lib" if os.name == "nt" else "libnethost.a") - 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["platform"] == "windows": + env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"]) if env.msvc: - env.Append(LINKFLAGS=mono_static_lib_file) - - env.Append(LINKFLAGS="Mincore.lib") - env.Append(LINKFLAGS="msvcrt.lib") - env.Append(LINKFLAGS="LIBCMT.lib") - env.Append(LINKFLAGS="Psapi.lib") + env.Append(LINKFLAGS="libnethost.lib") else: - 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(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"]) else: - mono_lib_file = find_file_in_dir(mono_lib_path, mono_lib_names, extensions=lib_suffixes) + is_apple = env["platform"] in ["macos", "ios"] + # is_macos = is_apple and not is_ios - if not mono_lib_file: - raise RuntimeError("Could not find mono library in: " + mono_lib_path) + # if is_ios and not is_ios_sim: + # env_mono.Append(CPPDEFINES=["IOS_DEVICE"]) - if env.msvc: - env.Append(LINKFLAGS=mono_lib_file) + if is_apple: + env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path]) else: - mono_lib_file_path = os.path.join(mono_lib_path, mono_lib_file) - env.Append(LINKFLAGS=mono_lib_file_path) + env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"]) - mono_bin_path = os.path.join(mono_root, "bin") - mono_dll_file = find_file_in_dir(mono_bin_path, mono_lib_names, prefixes=["", "lib"], extensions=[".dll"]) +def find_dotnet_app_host_dir(env): + dotnet_version = "6.0" - if not mono_dll_file: - raise RuntimeError("Could not find mono shared library in: " + mono_bin_path) + dotnet_root = env["dotnet_root"] - copy_file(mono_bin_path, "#bin", mono_dll_file) - else: - is_apple = env["platform"] in ["macos", "ios"] - is_macos = is_apple and not is_ios - - sharedlib_ext = ".dylib" if is_apple else ".so" - - mono_root = mono_prefix - mono_lib_path = "" - mono_so_file = "" - - 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_macos: - # Try with some known directories under macOS - hint_dirs = ["/Library/Frameworks/Mono.framework/Versions/Current", "/usr/local/var/homebrew/linked/mono"] - for hint_dir in hint_dirs: - if os.path.isdir(hint_dir): - mono_root = hint_dir - break - - # We can't use pkg-config to link mono statically, - # but we can still use it to find the mono root directory - if not mono_root and mono_static: - mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext) - if not mono_root: - raise RuntimeError( - "Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " - + "specify one manually with the 'mono_prefix' SCons parameter" - ) - - if is_ios and not is_ios_sim: - env_mono.Append(CPPDEFINES=["IOS_DEVICE"]) - - if mono_root: - print("Found Mono root directory: " + mono_root) - - mono_lib_path = os.path.join(mono_root, "lib") - - env.Append(LIBPATH=[mono_lib_path]) - env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) - - mono_lib = find_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) - - env_mono.Append(CPPDEFINES=["_REENTRANT"]) - - if mono_static: - if not is_javascript: - env.Append(LINKFLAGS=["-rdynamic"]) - - mono_lib_file = os.path.join(mono_lib_path, "lib" + mono_lib + ".a") - - if is_apple: - 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.ios.%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"]) - - if is_javascript: - env.Append(LIBS=["mono-icall-table", "mono-native", "mono-ilgen", "mono-ee-interp"]) - - 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") - - # 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"), - ] - ) - else: - env.Append(LIBS=[mono_lib]) - - if is_macos: - env.Append(LIBS=["iconv", "pthread"]) - elif is_android: - 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"]) - else: - env.Append(LIBS=["m", "rt", "dl", "pthread"]) + if not dotnet_root: + dotnet_cmd = find_dotnet_executable(env["arch"]) + if dotnet_cmd: + sdk_path = find_dotnet_sdk(dotnet_cmd, dotnet_version) + if sdk_path: + dotnet_root = os.path.abspath(os.path.join(sdk_path, os.pardir)) - if not mono_static: - mono_so_file = find_file_in_dir( - mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext] - ) + if not dotnet_root: + raise RuntimeError("Cannot find .NET Core Sdk") - if not mono_so_file: - raise RuntimeError("Could not find mono shared library in: " + mono_lib_path) - else: - assert not mono_static + print("Found .NET Core Sdk root directory: " + dotnet_root) - # TODO: Add option to force using pkg-config - print("Mono root directory not found. Using pkg-config instead") + dotnet_cmd = os.path.join(dotnet_root, "dotnet.exe" if os.name == "nt" else "dotnet") - env.ParseConfig("pkg-config monosgen-2 --libs") - env_mono.ParseConfig("pkg-config monosgen-2 --cflags") + runtime_identifier = determine_runtime_identifier(env) - tmpenv = Environment() - tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) - tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L") + # TODO: In the future, if it can't be found this way, we want to obtain it + # from the runtime.{runtime_identifier}.Microsoft.NETCore.DotNetAppHost NuGet package. + app_host_version = find_app_host_version(dotnet_cmd, dotnet_version) + if not app_host_version: + raise RuntimeError("Cannot find .NET app host for version: " + dotnet_version) - 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_file = file_found - break + def get_runtime_path(): + return os.path.join( + dotnet_root, + "packs", + "Microsoft.NETCore.App.Host." + runtime_identifier, + app_host_version, + "runtimes", + runtime_identifier, + "native", + ) - if not mono_so_file: - raise RuntimeError("Could not find mono shared library in: " + str(tmpenv["LIBPATH"])) + app_host_dir = get_runtime_path() + + # Some Linux distros use their distro name as the RID in these paths. + # If the initial generic path doesn't exist, try to get the RID from `dotnet --info`. + # The generic RID should still be the first choice. Some platforms like Windows 10 + # define the RID as `win10-x64` but still use the generic `win-x64` for directory names. + if not app_host_dir or not os.path.isdir(app_host_dir): + runtime_identifier = find_dotnet_cli_rid(dotnet_cmd) + app_host_dir = get_runtime_path() + + return app_host_dir + + +def determine_runtime_identifier(env): + # The keys are Godot's names, the values are the Microsoft's names. + # List: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog + names_map = { + "windows": "win", + "macos": "osx", + "linuxbsd": "linux", + } + arch_map = { + "x86_64": "x64", + "x86_32": "x86", + "arm64": "arm64", + "arm32": "arm", + } + platform = env["platform"] + if is_desktop(platform): + return "%s-%s" % (names_map[platform], arch_map[env["arch"]]) + else: + raise NotImplementedError() - 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, mono_so_file) - if not tools_enabled: - if is_desktop(env["platform"]): - if not mono_root: - mono_root = ( - subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip() - ) +def find_app_host_version(dotnet_cmd, search_version_str): + import subprocess + from distutils.version import LooseVersion - make_template_dir(env, mono_root) - elif is_android: - # Compress Android Mono Config - from . import make_android_mono_config + search_version = LooseVersion(search_version_str) - 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/") + try: + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") + lines = subprocess.check_output([dotnet_cmd, "--list-runtimes"], env=env).splitlines() - # Copy the required shared libraries - copy_mono_shared_libs(env, mono_root, None) - elif is_javascript: - pass # No data directory for this platform - elif is_ios: - pass # No data directory for this platform + for line_bytes in lines: + line = line_bytes.decode("utf-8") + if not line.startswith("Microsoft.NETCore.App "): + continue - if copy_mono_root: - if not mono_root: - mono_root = subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip() + parts = line.split(" ", 2) + if len(parts) < 3: + continue - if tools_enabled: - # Only supported for editor builds. - copy_mono_root_files(env, mono_root, mono_bcl) + version_str = parts[1] + version = LooseVersion(version_str) -def make_template_dir(env, mono_root): - from shutil import rmtree + if version >= search_version: + return version_str + except (subprocess.CalledProcessError, OSError) as e: + import sys - platform = env["platform"] - target = env["target"] + print(e, file=sys.stderr) - template_dir_name = "" + return "" - assert is_desktop(platform) - template_dir_name = "data.mono.%s.%s.%s" % (platform, env["bits"], target) +def find_dotnet_arch(dotnet_cmd): + import subprocess - output_dir = Dir("#bin").abspath - template_dir = os.path.join(output_dir, template_dir_name) + try: + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") + lines = subprocess.check_output([dotnet_cmd, "--info"], env=env).splitlines() - template_mono_root_dir = os.path.join(template_dir, "Mono") + for line_bytes in lines: + line = line_bytes.decode("utf-8") - if os.path.isdir(template_mono_root_dir): - rmtree(template_mono_root_dir) # Clean first + parts = line.split(":", 1) + if len(parts) < 2: + continue - # Copy etc/mono/ + arch_str = parts[0].strip() + if arch_str != "Architecture": + continue - template_mono_config_dir = os.path.join(template_mono_root_dir, "etc", "mono") - copy_mono_etc_dir(mono_root, template_mono_config_dir, platform) + arch_value = parts[1].strip() + arch_map = {"x64": "x86_64", "x86": "x86_32", "arm64": "arm64", "arm32": "arm32"} + return arch_map[arch_value] + except (subprocess.CalledProcessError, OSError) as e: + import sys - # Copy the required shared libraries + print(e, file=sys.stderr) + + return "" - copy_mono_shared_libs(env, mono_root, template_mono_root_dir) +def find_dotnet_sdk(dotnet_cmd, search_version_str): + import subprocess + from distutils.version import LooseVersion -def copy_mono_root_files(env, mono_root, mono_bcl): - from glob import glob - from shutil import copy - from shutil import rmtree + search_version = LooseVersion(search_version_str) - if not mono_root: - raise RuntimeError("Mono installation directory not found") + try: + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") + lines = subprocess.check_output([dotnet_cmd, "--list-sdks"], env=env).splitlines() - output_dir = Dir("#bin").abspath - editor_mono_root_dir = os.path.join(output_dir, "GodotSharp", "Mono") + for line_bytes in lines: + line = line_bytes.decode("utf-8") - if os.path.isdir(editor_mono_root_dir): - rmtree(editor_mono_root_dir) # Clean first + parts = line.split(" ", 1) + if len(parts) < 2: + continue - # Copy etc/mono/ + version_str = parts[0] - 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"]) + version = LooseVersion(version_str) - # Copy the required shared libraries + if version < search_version: + continue - copy_mono_shared_libs(env, mono_root, editor_mono_root_dir) + path_part = parts[1] + return path_part[1 : path_part.find("]")] + except (subprocess.CalledProcessError, OSError) as e: + import sys - # Copy framework assemblies + print(e, file=sys.stderr) - mono_framework_dir = mono_bcl or os.path.join(mono_root, "lib", "mono", "4.5") - mono_framework_facades_dir = os.path.join(mono_framework_dir, "Facades") + return "" - 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) +def find_dotnet_cli_rid(dotnet_cmd): + import subprocess - for assembly in glob(os.path.join(mono_framework_dir, "*.dll")): - copy(assembly, editor_mono_framework_dir) - for assembly in glob(os.path.join(mono_framework_facades_dir, "*.dll")): - copy(assembly, editor_mono_framework_facades_dir) + try: + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") + lines = subprocess.check_output([dotnet_cmd, "--info"], env=env).splitlines() + for line_bytes in lines: + line = line_bytes.decode("utf-8") + if not line.startswith(" RID:"): + continue -def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform): - from distutils.dir_util import copy_tree - from glob import glob - from shutil import copy + parts = line.split() + if len(parts) < 2: + continue - if not os.path.isdir(target_mono_config_dir): - os.makedirs(target_mono_config_dir) + return parts[1] + except (subprocess.CalledProcessError, OSError) as e: + import sys - mono_etc_dir = os.path.join(mono_root, "etc", "mono") - if not os.path.isdir(mono_etc_dir): - mono_etc_dir = "" - etc_hint_dirs = [] - if platform != "windows": - etc_hint_dirs += ["/etc/mono", "/usr/local/etc/mono"] - if "MONO_CFG_DIR" in os.environ: - etc_hint_dirs += [os.path.join(os.environ["MONO_CFG_DIR"], "mono")] - for etc_hint_dir in etc_hint_dirs: - if os.path.isdir(etc_hint_dir): - mono_etc_dir = etc_hint_dir - break - if not mono_etc_dir: - raise RuntimeError("Mono installation etc directory not found") + print(e, file=sys.stderr) - 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")) + return "" - for file in glob(os.path.join(mono_etc_dir, "*")): - if os.path.isfile(file): - copy(file, target_mono_config_dir) +ENV_PATH_SEP = ";" if os.name == "nt" else ":" -def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): - from shutil import copy - def copy_if_exists(src, dst): - if os.path.isfile(src): - copy(src, dst) +def find_dotnet_executable(arch): + is_windows = os.name == "nt" + windows_exts = os.environ["PATHEXT"].split(ENV_PATH_SEP) if is_windows else None + path_dirs = os.environ["PATH"].split(ENV_PATH_SEP) - platform = env["platform"] + search_dirs = path_dirs + [os.getcwd()] # cwd is last in the list - 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") + for dir in path_dirs: + search_dirs += [ + os.path.join(dir, "x64"), + os.path.join(dir, "x86"), + os.path.join(dir, "arm64"), + os.path.join(dir, "arm32"), + ] # search subfolders for cross compiling - if not os.path.isdir(target_mono_bin_dir): - os.makedirs(target_mono_bin_dir) + for dir in search_dirs: + path = os.path.join(dir, "dotnet") - 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"), - ) + if is_windows: + for extension in windows_exts: + path_with_ext = path + extension - # For newer versions - 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") - ) + if os.path.isfile(path_with_ext) and os.access(path_with_ext, os.X_OK): + sdk_arch = find_dotnet_arch(path_with_ext) + if sdk_arch == arch or arch == "": + return path_with_ext + else: + if os.path.isfile(path) and os.access(path, os.X_OK): + sdk_arch = find_dotnet_arch(path) + if sdk_arch == arch or arch == "": + return path - if not os.path.isdir(target_mono_lib_dir): - os.makedirs(target_mono_lib_dir) - - lib_file_names = [] - if platform == "macos": - 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", - ] - ] - - for lib_file_name in lib_file_names: - copy_if_exists(os.path.join(mono_root, "lib", lib_file_name), target_mono_lib_dir) - - -def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): - tmpenv = Environment() - tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) - tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L") - for hint_dir in tmpenv["LIBPATH"]: - name_found = find_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 deleted file mode 100644 index 43c1ec8f8a..0000000000 --- a/modules/mono/build_scripts/mono_reg_utils.py +++ /dev/null @@ -1,112 +0,0 @@ -import os -import platform - -if os.name == "nt": - import winreg - - -def _reg_open_key(key, subkey): - try: - return winreg.OpenKey(key, subkey) - except OSError: - if platform.architecture()[0] == "32bit": - bitness_sam = winreg.KEY_WOW64_64KEY - else: - bitness_sam = winreg.KEY_WOW64_32KEY - return winreg.OpenKey(key, subkey, 0, winreg.KEY_READ | bitness_sam) - - -def _reg_open_key_bits(key, subkey, bits): - sam = winreg.KEY_READ - - 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": - # Force 64bit process to search in 32bit registry - sam |= winreg.KEY_WOW64_32KEY - - return winreg.OpenKey(key, subkey, 0, sam) - - -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] - return value - except OSError: - return None - - -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] - if default_clr: - return _find_mono_in_reg(subkey + "\\" + default_clr, bits) - return None - except OSError: - return None - - -def find_mono_root_dir(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) - if root_dir is not None: - return str(root_dir) - return "" - - -def find_msbuild_tools_path_reg(): - import subprocess - - vswhere = os.getenv("PROGRAMFILES(X86)") - if not vswhere: - vswhere = os.getenv("PROGRAMFILES") - vswhere += r"\Microsoft Visual Studio\Installer\vswhere.exe" - - vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"] - - try: - lines = subprocess.check_output([vswhere] + vswhere_args).splitlines() - - for line in lines: - parts = line.decode("utf-8").split(":", 1) - - if len(parts) < 2 or parts[0] != "installationPath": - continue - - val = parts[1].strip() - - if not val: - 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") - if os.path.isdir(msbuild_dir): - return msbuild_dir - - # Directory name "15.0" is used in VS 2017 - return os.path.join(val, "MSBuild\\15.0\\Bin") - - raise ValueError("Cannot find `installationPath` entry") - except ValueError as e: - print("Error reading output from vswhere: " + e.message) - except subprocess.CalledProcessError as e: - print(e.output) - except OSError as e: - print(e) - - # Try to find 14.0 in the Registry - - try: - 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] - return value - except OSError: - return "" diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py deleted file mode 100644 index 6a621c3c8b..0000000000 --- a/modules/mono/build_scripts/solution_builder.py +++ /dev/null @@ -1,145 +0,0 @@ -import os - - -verbose = False - - -def find_dotnet_cli(): - import os.path - - if os.name == "nt": - for hint_dir in os.environ["PATH"].split(os.pathsep): - hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, "dotnet") - if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): - return hint_path - if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): - return hint_path + ".exe" - else: - for hint_dir in os.environ["PATH"].split(os.pathsep): - hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, "dotnet") - if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): - return hint_path - - -def find_msbuild_unix(): - import os.path - import sys - - hint_dirs = [] - if sys.platform == "darwin": - hint_dirs[:0] = [ - "/Library/Frameworks/Mono.framework/Versions/Current/bin", - "/usr/local/var/homebrew/linked/mono/bin", - ] - - for hint_dir in hint_dirs: - hint_path = os.path.join(hint_dir, "msbuild") - if os.path.isfile(hint_path): - return hint_path - elif os.path.isfile(hint_path + ".exe"): - return hint_path + ".exe" - - for hint_dir in os.environ["PATH"].split(os.pathsep): - hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, "msbuild") - if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): - return hint_path - if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): - return hint_path + ".exe" - - return None - - -def find_msbuild_windows(env): - from .mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg - - mono_root = env["mono_prefix"] or find_mono_root_dir(env["bits"]) - - if not mono_root: - raise RuntimeError("Cannot find mono root directory") - - mono_bin_dir = os.path.join(mono_root, "bin") - msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat") - - msbuild_tools_path = find_msbuild_tools_path_reg() - - if msbuild_tools_path: - return (os.path.join(msbuild_tools_path, "MSBuild.exe"), {}) - - if os.path.isfile(msbuild_mono): - # The (Csc/Vbc/Fsc)ToolExe environment variables are required when - # building with Mono's MSBuild. They must point to the batch files - # in Mono's bin directory to make sure they are executed with Mono. - mono_msbuild_env = { - "CscToolExe": os.path.join(mono_bin_dir, "csc.bat"), - "VbcToolExe": os.path.join(mono_bin_dir, "vbc.bat"), - "FscToolExe": os.path.join(mono_bin_dir, "fsharpc.bat"), - } - return (msbuild_mono, mono_msbuild_env) - - return None - - -def run_command(command, args, env_override=None, name=None): - def cmd_args_to_str(cmd_args): - return " ".join([arg if not " " in arg else '"%s"' % arg for arg in cmd_args]) - - args = [command] + args - - if name is None: - name = os.path.basename(command) - - if verbose: - print("Running '%s': %s" % (name, cmd_args_to_str(args))) - - import subprocess - - try: - if env_override is None: - subprocess.check_call(args) - else: - subprocess.check_call(args, env=env_override) - except subprocess.CalledProcessError as e: - raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode)) - - -def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): - global verbose - verbose = env["verbose"] - - msbuild_env = os.environ.copy() - - # Needed when running from Developer Command Prompt for VS - if "PLATFORM" in msbuild_env: - del msbuild_env["PLATFORM"] - - msbuild_args = [] - - dotnet_cli = find_dotnet_cli() - - if dotnet_cli: - msbuild_path = dotnet_cli - msbuild_args += ["msbuild"] # `dotnet msbuild` command - else: - # Find MSBuild - if os.name == "nt": - msbuild_info = find_msbuild_windows(env) - if msbuild_info is None: - raise RuntimeError("Cannot find MSBuild executable") - msbuild_path = msbuild_info[0] - msbuild_env.update(msbuild_info[1]) - else: - msbuild_path = find_msbuild_unix() - if msbuild_path is None: - raise RuntimeError("Cannot find MSBuild executable") - - print("MSBuild path: " + msbuild_path) - - # Build solution - - msbuild_args += [solution_path, "/restore", "/t:Build", "/p:Configuration=" + build_config] - msbuild_args += extra_msbuild_args - - run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild") diff --git a/modules/mono/config.py b/modules/mono/config.py index d895d2d92d..b599414a2c 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,4 +1,6 @@ -supported_platforms = ["windows", "macos", "linuxbsd", "server", "android", "haiku", "javascript", "ios"] +# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "haiku", "javascript", "ios"] +# Eventually support for each them should be added back (except Haiku if not supported by .NET Core) +supported_platforms = ["windows", "macos", "linuxbsd"] def can_build(env, platform): @@ -13,26 +15,11 @@ def get_opts(platform): return [ PathVariable( - "mono_prefix", - "Path to the Mono installation directory for the target platform and architecture", + "dotnet_root", + "Path to the .NET Sdk installation directory for the target platform and architecture", "", PathVariable.PathAccept, ), - PathVariable( - "mono_bcl", - "Path to a custom Mono BCL (Base Class Library) directory for the target platform", - "", - PathVariable.PathAccept, - ), - BoolVariable("mono_static", "Statically link Mono", default_mono_static), - BoolVariable("mono_glue", "Build with the Mono glue sources", True), - BoolVariable("build_cil", "Build C# solutions", True), - BoolVariable( - "copy_mono_root", "Make a copy of the Mono installation directory to bundle with the editor", True - ), - BoolVariable( - "mono_bundles_zlib", "Specify if the Mono runtime was built with bundled zlib", default_mono_bundles_zlib - ), ] @@ -44,13 +31,6 @@ def configure(env): env.add_module_version_string("mono") - 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 [ diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 475b483d6c..8b135051c5 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -30,8 +30,6 @@ #include "csharp_script.h" -#include <mono/metadata/threads.h> -#include <mono/metadata/tokentype.h> #include <stdint.h> #include "core/config/project_settings.h" @@ -57,10 +55,8 @@ #endif #include "godotsharp_dirs.h" +#include "managed_callable.h" #include "mono_gd/gd_mono_cache.h" -#include "mono_gd/gd_mono_class.h" -#include "mono_gd/gd_mono_marshal.h" -#include "mono_gd/gd_mono_utils.h" #include "signal_awaiter_utils.h" #include "utils/macros.h" #include "utils/string_utils.h" @@ -69,17 +65,8 @@ #ifdef TOOLS_ENABLED static bool _create_project_solution_if_needed() { - String sln_path = GodotSharpDirs::get_project_sln_path(); - String csproj_path = GodotSharpDirs::get_project_csproj_path(); - - 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() == nullptr); - return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution"); - } - - return true; + CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr); + return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolutionIfNeeded"); } #endif @@ -118,26 +105,21 @@ void CSharpLanguage::init() { } #endif - gdmono = memnew(GDMono); - gdmono->initialize(); - #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) - // Generate bindings here, before loading assemblies. 'initialize_load_assemblies' aborts - // the applications if the api assemblies or the main tools assembly is missing, but this - // is not a problem for BindingsGenerator as it only needs the tools project editor assembly. + // Generate the bindings here, before loading assemblies. The Godot assemblies + // may be missing if the glue wasn't generated yet in order to build them. List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); BindingsGenerator::handle_cmdline_args(cmdline_args); #endif -#ifndef MONO_GLUE_ENABLED - print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'"); -#endif + gdmono = memnew(GDMono); + gdmono->initialize(); +#ifdef TOOLS_ENABLED if (gdmono->is_runtime_initialized()) { gdmono->initialize_load_assemblies(); } -#ifdef TOOLS_ENABLED EditorNode::add_init_callback(&_editor_init_callback); #endif } @@ -596,23 +578,19 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() return Vector<StackInfo>(); } _recursion_flag_ = true; - SCOPE_EXIT { _recursion_flag_ = false; }; - - GD_MONO_SCOPE_THREAD_ATTACH; + SCOPE_EXIT { + _recursion_flag_ = false; + }; - if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) { + if (!gdmono->is_runtime_initialized()) { return Vector<StackInfo>(); } - MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr()); - - MonoBoolean need_file_info = true; - void *ctor_args[1] = { &need_file_info }; - - CACHED_METHOD(System_Diagnostics_StackTrace, ctor_bool)->invoke_raw(stack_trace, ctor_args); - Vector<StackInfo> si; - si = stack_trace_get_info(stack_trace); + + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.DebuggingUtils_GetCurrentStackInfo(&si); + } return si; #else @@ -620,63 +598,6 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #endif } -#ifdef DEBUG_ENABLED -Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { - // Printing an error here will result in endless recursion, so we must be careful - static thread_local bool _recursion_flag_ = false; - if (_recursion_flag_) { - return Vector<StackInfo>(); - } - _recursion_flag_ = true; - SCOPE_EXIT { _recursion_flag_ = false; }; - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoException *exc = nullptr; - - MonoArray *frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames).invoke(p_stack_trace, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - return Vector<StackInfo>(); - } - - int frame_count = mono_array_length(frames); - - if (frame_count <= 0) { - return Vector<StackInfo>(); - } - - Vector<StackInfo> si; - si.resize(frame_count); - - for (int i = 0; i < frame_count; i++) { - StackInfo &sif = si.write[i]; - MonoObject *frame = mono_array_get(frames, MonoObject *, i); - - MonoString *file_name; - int file_line_num; - MonoString *method_decl; - CACHED_METHOD_THUNK(DebuggingUtils, GetStackFrameInfo).invoke(frame, &file_name, &file_line_num, &method_decl, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - return Vector<StackInfo>(); - } - - // TODO - // what if the StackFrame method is null (method_decl is empty). should we skip this frame? - // can reproduce with a MissingMethodException on internal calls - - sif.file = GDMonoMarshal::mono_string_to_godot(file_name); - sif.line = file_line_num; - sif.func = GDMonoMarshal::mono_string_to_godot(method_decl); - } - - return si; -} -#endif - void CSharpLanguage::post_unsafe_reference(Object *p_obj) { #ifdef DEBUG_ENABLED MutexLock lock(unsafe_object_references_lock); @@ -698,48 +619,36 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { } void CSharpLanguage::frame() { - if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != nullptr) { - const Ref<MonoGCHandleRef> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle; - - if (task_scheduler_handle.is_valid()) { - MonoObject *task_scheduler = task_scheduler_handle->get_target(); - - if (task_scheduler) { - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(GodotTaskScheduler, Activate).invoke(task_scheduler, &exc); - - if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); - } - } - } + if (gdmono && gdmono->is_runtime_initialized() && GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_FrameCallback(); } } struct CSharpScriptDepSort { - // must support sorting so inheritance works properly (parent must be reloaded first) + // Must support sorting so inheritance works properly (parent must be reloaded first) bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const { if (A == B) { - return false; // shouldn't happen but.. + // Shouldn't happen but just in case... + return false; } - GDMonoClass *I = B->base; + const Script *I = B->get_base_script().ptr(); while (I) { - if (I == A->script_class) { + if (I == A.ptr()) { // A is a base of B return true; } - I = I->get_parent_class(); + I = I->get_base_script().ptr(); } - return false; // not a base + // A isn't a base of B + return false; } }; void CSharpLanguage::reload_all_scripts() { #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { - GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(false); } #endif @@ -756,7 +665,6 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { - GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(p_soft_reload); } #endif @@ -768,28 +676,28 @@ bool CSharpLanguage::is_assembly_reloading_needed() { return false; } - GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); - - String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name(); - - appname_safe += ".dll"; + String assembly_path = gdmono->get_project_assembly_path(); - if (proj_assembly) { - String proj_asm_path = 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)) { - return false; // No assembly to load - } + if (!assembly_path.is_empty()) { + if (!FileAccess::exists(assembly_path)) { + return false; // No assembly to load } - if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) { + if (FileAccess::get_modified_time(assembly_path) <= gdmono->get_project_assembly_modified_time()) { return false; // Already up to date } } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) { + String assembly_name = ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); + + if (assembly_name.is_empty()) { + assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); + } + + assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() + .plus_file(assembly_name + ".dll"); + assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path); + + if (!FileAccess::exists(assembly_path)) { return false; // No assembly to load } } @@ -802,6 +710,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } + // TODO: + // Currently, this reloads all scripts, including those whose class is not part of the + // assembly load context being unloaded. As such, we unnecessarily reload GodotTools. + + print_verbose(".NET: Reloading assemblies..."); + // There is no soft reloading with Mono. It's always hard reloading. List<Ref<CSharpScript>> scripts; @@ -824,18 +738,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { 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(); + ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr); 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; - } + bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle( + managed_callable->delegate_handle, &serialized_data); if (success) { ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data); @@ -864,17 +772,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // If someone removes a script from a node, deletes the script, builds, adds a script to the // same node, then builds again, the script might have no path and also no script_class. In // that case, we can't (and don't need to) reload it. - if (script->get_path().is_empty() && !script->script_class) { + if (script->get_path().is_empty() && !script->valid) { continue; } to_reload.push_back(script); - if (script->get_path().is_empty()) { - script->tied_class_name_for_reload = script->script_class->get_name_for_lookup(); - script->tied_class_namespace_for_reload = script->script_class->get_namespace(); - } - // Script::instances are deleted during managed object disposal, which happens on domain finalize. // Only placeholders are kept. Therefore we need to keep a copy before that happens. @@ -907,17 +810,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); - // Call OnBeforeSerialize - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { - obj->get_script_instance()->call(string_names.on_before_serialize); - } + // Call OnBeforeSerialize and save instance info - // Save instance info CSharpScript::StateBackup state; - // TODO: Proper state backup (Not only variants, serialize managed state of scripts) - csi->get_properties_state_for_reloading(state.properties); - csi->get_event_signals_state_for_reloading(state.event_signals); + Dictionary properties; + + GDMonoCache::managed_callbacks.CSharpInstanceBridge_SerializeState( + csi->get_gchandle_intptr(), &properties, &state.event_signals); + + for (const Variant *s = properties.next(nullptr); s != nullptr; s = properties.next(s)) { + StringName name = *s; + Variant value = properties[*s]; + state.properties.push_back(Pair<StringName, Variant>(name, value)); + } owners_map[obj->get_instance_id()] = state; } @@ -934,7 +840,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } // Do domain reload - if (gdmono->reload_scripts_domain() != OK) { + if (gdmono->reload_project_assemblies() != OK) { // Failed to reload the scripts domain // Make sure to add the scripts back to their owners before returning for (Ref<CSharpScript> &scr : to_reload) { @@ -965,6 +871,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->pending_reload_state.erase(obj_id); } + + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); } return; @@ -976,53 +885,27 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #ifdef TOOLS_ENABLED script->exports_invalidated = true; #endif - script->signals_invalidated = true; if (!script->get_path().is_empty()) { script->reload(p_soft_reload); if (!script->valid) { script->pending_reload_instances.clear(); + script->pending_reload_state.clear(); continue; } } else { - const StringName &class_namespace = script->tied_class_namespace_for_reload; - const StringName &class_name = script->tied_class_name_for_reload; - GDMonoAssembly *project_assembly = gdmono->get_project_assembly(); - - // Search in project and tools assemblies first as those are the most likely to have the class - GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : 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) : nullptr); - } -#endif + bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(script.ptr()); - if (!script_class) { - script_class = gdmono->get_class(class_namespace, class_name); - } - - if (!script_class) { - // The class was removed, can't reload - script->pending_reload_instances.clear(); - continue; - } - - bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class); - if (!obj_type) { - // The class no longer inherits Godot.Object, can't reload + if (!success) { + // Couldn't reload script->pending_reload_instances.clear(); + script->pending_reload_state.clear(); continue; } - - GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class); - - CSharpScript::initialize_for_managed_type(script, script_class, native); } - StringName native_name = NATIVE_GDMONOCLASS_NAME(script->native); + StringName native_name = script->get_instance_base_type(); { for (const ObjectID &obj_id : script->pending_reload_instances) { @@ -1087,57 +970,25 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { ERR_CONTINUE(!obj->get_script_instance()); - // TODO: Restore serialized state - CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; - for (const Pair<StringName, Variant> &G : state_backup.properties) { - obj->get_script_instance()->set(G.first, G.second); - } - CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); if (csi) { - for (const Pair<StringName, Array> &G : state_backup.event_signals) { - const StringName &name = G.first; - const Array &serialized_data = G.second; - - HashMap<StringName, CSharpScript::EventSignal>::Iterator match = script->event_signals.find(name); + Dictionary properties; - 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"); - } + for (const Pair<StringName, Variant> &G : state_backup.properties) { + properties[G.first] = G.second; } - // Call OnAfterDeserialization - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { - obj->get_script_instance()->call(string_names.on_after_deserialize); - } + // Restore serialized state and call OnAfterDeserialization + GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState( + csi->get_gchandle_intptr(), &properties, &state_backup.event_signals); } } script->pending_reload_instances.clear(); + script->pending_reload_state.clear(); } // Deserialize managed callables @@ -1148,20 +999,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { 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); + GCHandleIntPtr delegate = { nullptr }; - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - continue; - } + bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( + &serialized_data, &delegate); if (success) { - ERR_CONTINUE(delegate == nullptr); - managed_callable->set_delegate(delegate); + ERR_CONTINUE(delegate.value == nullptr); + managed_callable->delegate_handle = delegate; } else if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print("Failed to deserialize delegate\n"); } @@ -1180,60 +1025,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #endif -void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) { - if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) { - return; - } - - MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute)); - String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr); - - dotnet_script_lookup_map[path] = DotNetScriptLookupInfo( - p_class->get_namespace(), p_class->get_name(), p_class); -} - -void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) { - if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) { - MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute)); - bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr); - - if (requires_lookup) { - // This is supported for scenarios where specifying all types would be cumbersome, - // such as when disabling C# source generators (for whatever reason) or when using a - // language other than C# that has nothing similar to source generators to automate it. - MonoImage *image = p_assembly->get_image(); - - int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - - for (int i = 1; i < rows; i++) { - // We don't search inner classes, only top-level. - MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - - if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { - continue; - } - - GDMonoClass *current = p_assembly->get_class(mono_class); - if (current) { - lookup_script_for_class(current); - } - } - } else { - // This is the most likely scenario as we use C# source generators - MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr); - - int length = mono_array_length(script_types); - - for (int i = 0; i < length; i++) { - MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i); - ManagedType type = ManagedType::from_reftype(reftype); - ERR_CONTINUE(!type.type_class); - lookup_script_for_class(type.type_class); - } - } - } -} - void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("cs"); } @@ -1248,22 +1039,6 @@ bool CSharpLanguage::overrides_external_editor() { } #endif -void CSharpLanguage::thread_enter() { -#if 0 - if (gdmono->is_runtime_initialized()) { - GDMonoUtils::attach_current_thread(); - } -#endif -} - -void CSharpLanguage::thread_exit() { -#if 0 - if (gdmono->is_runtime_initialized()) { - GDMonoUtils::detach_current_thread(); - } -#endif -} - bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { // Not a parser error in our case, but it's still used for other type of errors if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { @@ -1289,49 +1064,35 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { } } -void CSharpLanguage::_on_scripts_domain_unloaded() { - for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) { - CSharpScriptBinding &script_binding = E.value; - script_binding.gchandle.release(); - script_binding.inited = false; - } - +void CSharpLanguage::_on_scripts_domain_about_to_unload() { #ifdef GD_MONO_HOT_RELOAD { 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; + managed_callable->release_delegate_handle(); } } #endif - - dotnet_script_lookup_map.clear(); } #ifdef TOOLS_ENABLED void CSharpLanguage::_editor_init_callback() { - register_editor_internal_calls(); - - // Initialize GodotSharpEditor - - GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor"); - CRASH_COND(editor_klass == nullptr); + // Load GodotTools and initialize GodotSharpEditor - MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr()); - CRASH_COND(mono_object == nullptr); + int32_t interop_funcs_size = 0; + const void **interop_funcs = godotsharp::get_editor_interop_funcs(interop_funcs_size); - MonoException *exc = nullptr; - GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc); - UNHANDLED_EXCEPTION(exc); + Object *editor_plugin_obj = GDMono::get_singleton()->get_plugin_callbacks().LoadToolsAssemblyCallback( + GodotSharpDirs::get_data_editor_tools_dir().plus_file("GodotTools.dll").utf16(), + interop_funcs, interop_funcs_size); + CRASH_COND(editor_plugin_obj == nullptr); - EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>( - GDMonoMarshal::mono_object_to_variant(mono_object).operator Object *()); + EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(editor_plugin_obj); CRASH_COND(godotsharp_editor == nullptr); - // Enable it as a plugin + // Add plugin to EditorNode and enable it EditorNode::add_editor_plugin(godotsharp_editor); ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B); godotsharp_editor->enable_plugin(); @@ -1352,24 +1113,24 @@ void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) { } } -void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle) { - uint32_t pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(p_expected_obj); // We might lock after this, so pin it - - if (!p_gchandle.is_released()) { // Do not lock unnecessarily +void CSharpLanguage::release_script_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, MonoGCHandleData &r_gchandle) { + if (!r_gchandle.is_released() && r_gchandle.get_intptr() == p_gchandle_to_free) { // Do not lock unnecessarily MutexLock lock(get_singleton()->script_gchandle_release_mutex); - - 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 == nullptr) { - p_gchandle.release(); + if (!r_gchandle.is_released() && r_gchandle.get_intptr() == p_gchandle_to_free) { + r_gchandle.release(); } } +} - GDMonoUtils::free_gchandle(pinned_gchandle); +void CSharpLanguage::release_binding_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, CSharpScriptBinding &r_script_binding) { + MonoGCHandleData &gchandle = r_script_binding.gchandle; + if (!gchandle.is_released() && gchandle.get_intptr() == p_gchandle_to_free) { // Do not lock unnecessarily + MutexLock lock(get_singleton()->script_gchandle_release_mutex); + if (!gchandle.is_released() && gchandle.get_intptr() == p_gchandle_to_free) { + gchandle.release(); + r_script_binding.inited = false; // Here too, to be thread safe + } + } } CSharpLanguage::CSharpLanguage() { @@ -1401,18 +1162,23 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b ERR_FAIL_NULL_V(classinfo, false); type_name = classinfo->name; - GDMonoClass *type_class = GDMonoUtils::type_get_proxy_class(type_name); + bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), type_name); + ERR_FAIL_COND_V_MSG(!parent_is_object_class, false, + "Type inherits from native type '" + type_name + "', so it can't be instantiated in object of type: '" + p_object->get_class() + "'."); - ERR_FAIL_NULL_V(type_class, false); +#ifdef DEBUG_ENABLED + CRASH_COND(!r_script_binding.gchandle.is_released()); +#endif - MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(type_class, type_name, p_object); + GCHandleIntPtr strong_gchandle = + GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding( + &type_name, p_object); - ERR_FAIL_NULL_V(mono_object, false); + ERR_FAIL_NULL_V(strong_gchandle.value, false); r_script_binding.inited = true; r_script_binding.type_name = type_name; - r_script_binding.wrapper_class = type_class; // cache - r_script_binding.gchandle = MonoGCHandleData::new_strong_handle(mono_object); + r_script_binding.gchandle = MonoGCHandleData(strong_gchandle, gdmono::GCHandleType::STRONG_HANDLE); r_script_binding.owner = p_object; // Tie managed to unmanaged @@ -1455,7 +1221,7 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin if (GDMono::get_singleton() == nullptr) { #ifdef DEBUG_ENABLED - CRASH_COND(!csharp_lang->script_bindings.is_empty()); + CRASH_COND(csharp_lang && !csharp_lang->script_bindings.is_empty()); #endif // Mono runtime finalized, all the gchandle bindings were already released return; @@ -1465,8 +1231,6 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there } - GD_MONO_ASSERT_THREAD_ATTACHED; - { MutexLock lock(csharp_lang->language_bind_mutex); @@ -1477,11 +1241,11 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin if (script_binding.inited) { // Set the native instance field to IntPtr.Zero, if not yet garbage collected. // This is done to avoid trying to dispose the native instance from Dispose(bool). - MonoObject *mono_object = script_binding.gchandle.get_target(); - if (mono_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, nullptr); - } + GDMonoCache::managed_callbacks.ScriptManagerBridge_SetGodotObjectPtr( + script_binding.gchandle.get_intptr(), nullptr); + script_binding.gchandle.release(); + script_binding.inited = false; } csharp_lang->script_bindings.erase(data); @@ -1510,41 +1274,49 @@ GDNativeBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, if (p_reference) { // Refcount incremented if (refcount > 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(); - if (!target) { + // Release the current weak handle and replace it with a strong handle. + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = false; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { return false; // Called after the managed side was collected, so nothing to do here } - // Release the current weak handle and replace it with a strong handle. - MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target); - gchandle.release(); - gchandle = strong_gchandle; + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE); } return false; } else { // Refcount decremented 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(); - if (!target) { + // Release the current strong handle and replace it with a weak handle. + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = true; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { return refcount == 0; // Called after the managed side was collected, so nothing to do here } - // Release the current strong handle and replace it with a weak handle. - MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target); - gchandle.release(); - gchandle = weak_gchandle; + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE); return false; } @@ -1589,214 +1361,160 @@ void CSharpLanguage::set_instance_binding(Object *p_object, void *p_binding) { bool CSharpLanguage::has_instance_binding(Object *p_object) { return p_object->has_instance_binding(get_singleton()); } +void CSharpLanguage::tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted) { + // This method should not fail -CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { - CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); - - RefCounted *rc = Object::cast_to<RefCounted>(p_owner); + CRASH_COND(!p_unmanaged); - instance->base_ref_counted = rc != nullptr; - instance->owner = p_owner; - instance->gchandle = p_gchandle; + // All mono objects created from the managed world (e.g.: 'new Player()') + // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - if (instance->base_ref_counted) { - instance->_reference_owner_unsafe(); - } + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); - p_script->instances.insert(p_owner); + CRASH_COND(p_ref_counted != (bool)rc); - return instance; -} + MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr, + p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE); -MonoObject *CSharpInstance::get_mono_object() const { - ERR_FAIL_COND_V(gchandle.is_released(), nullptr); - return gchandle.get_target(); -} + // If it's just a wrapper Godot class and not a custom inheriting class, then attach a + // script binding instead. One of the advantages of this is that if a script is attached + // later and it's not a C# script, then the managed object won't have to be disposed. + // Another reason for doing this is that this instance could outlive CSharpLanguage, which would + // be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621 -Object *CSharpInstance::get_owner() { - return owner; -} - -bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { - ERR_FAIL_COND_V(!script.is_valid(), false); - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); + CSharpScriptBinding script_binding; - GDMonoClass *top = script->script_class; + script_binding.inited = true; + script_binding.type_name = *p_native_name; + script_binding.gchandle = gchandle; + script_binding.owner = p_unmanaged; - while (top && top != script->native) { - GDMonoField *field = top->get_field(p_name); + if (p_ref_counted) { + // Unsafe refcount increment. The managed instance also counts as a reference. + // This way if the unmanaged world has no references to our owner + // but the managed instance is alive, the refcount will be 1 instead of 0. + // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) - if (field) { - field->set_value_from_variant(mono_object, p_value); - return true; + // May not me referenced yet, so we must use init_ref() instead of reference() + if (rc->init_ref()) { + CSharpLanguage::get_singleton()->post_unsafe_reference(rc); } + } - GDMonoProperty *property = top->get_property(p_name); - - if (property) { - property->set_value_from_variant(mono_object, p_value); - return true; - } + // The object was just created, no script instance binding should have been attached + CRASH_COND(CSharpLanguage::has_instance_binding(p_unmanaged)); - top = top->get_parent_class(); + void *data; + { + MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); + data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(p_unmanaged, script_binding); } - // Call _set + // Should be thread safe because the object was just created and nothing else should be referencing it + CSharpLanguage::set_instance_binding(p_unmanaged, data); +} - top = script->script_class; +void CSharpLanguage::tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted) { + // This method should not fail - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_set), 2); + Ref<CSharpScript> script = *p_script; + // We take care of destructing this reference here, so the managed code won't need to do another P/Invoke call + p_script->~Ref(); - if (method) { - Variant name = p_name; - const Variant *args[2] = { &name, &p_value }; + CRASH_COND(!p_unmanaged); - MonoObject *ret = method->invoke(mono_object, args); + // All mono objects created from the managed world (e.g.: 'new Player()') + // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret)) { - return true; - } + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); - break; - } + CRASH_COND(p_ref_counted != (bool)rc); - top = top->get_parent_class(); - } + MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr, + p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE); - return false; -} + CRASH_COND(script.is_null()); -bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { - ERR_FAIL_COND_V(!script.is_valid(), false); - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); + CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(p_unmanaged, script.ptr(), gchandle); - GDMonoClass *top = script->script_class; + p_unmanaged->set_script_and_instance(script, csharp_instance); - while (top && top != script->native) { - GDMonoField *field = top->get_field(p_name); + csharp_instance->connect_event_signals(); +} - if (field) { - MonoObject *value = field->get_value(mono_object); - r_ret = GDMonoMarshal::mono_object_to_variant(value); - return true; - } +void CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged) { + // This method should not fail - GDMonoProperty *property = top->get_property(p_name); + CRASH_COND(!p_unmanaged); - if (property) { - MonoException *exc = nullptr; - MonoObject *value = property->get_value(mono_object, &exc); - if (exc) { - r_ret = Variant(); - GDMonoUtils::set_pending_exception(exc); - } else { - r_ret = GDMonoMarshal::mono_object_to_variant(value); - } - return true; - } + CSharpInstance *instance = CAST_CSHARP_INSTANCE(p_unmanaged->get_script_instance()); - top = top->get_parent_class(); + if (!instance) { + // Native bindings don't need post-setup + return; } - // Call _get + CRASH_COND(!instance->gchandle.is_released()); - top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get), 1); - - if (method) { - Variant name = p_name; - const Variant *args[1] = { &name }; - - MonoObject *ret = method->invoke(mono_object, args); - - if (ret) { - r_ret = GDMonoMarshal::mono_object_to_variant(ret); - return true; - } + // Tie managed to unmanaged + instance->gchandle = MonoGCHandleData(p_gchandle_intptr, gdmono::GCHandleType::STRONG_HANDLE); - break; - } + if (instance->base_ref_counted) { + instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) + } - top = top->get_parent_class(); + { + MutexLock lock(CSharpLanguage::get_singleton()->get_script_instances_mutex()); + // instances is a set, so it's safe to insert multiple times (e.g.: from _internal_new_managed) + instance->script->instances.insert(instance->owner); } - return false; + instance->connect_event_signals(); } -void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) { - List<PropertyInfo> property_list; - get_property_list(&property_list); - - for (const PropertyInfo &prop_info : property_list) { - Pair<StringName, Variant> state_pair; - state_pair.first = prop_info.name; +CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); - ManagedType managedType; + RefCounted *rc = Object::cast_to<RefCounted>(p_owner); - GDMonoField *field = nullptr; - GDMonoClass *top = script->script_class; - while (top && top != script->native) { - field = top->get_field(state_pair.first); - if (field) { - break; - } + instance->base_ref_counted = rc != nullptr; + instance->owner = p_owner; + instance->gchandle = p_gchandle; - top = top->get_parent_class(); - } - if (!field) { - continue; // Properties ignored. We get the property baking fields instead. - } + if (instance->base_ref_counted) { + instance->_reference_owner_unsafe(); + } - managedType = field->get_type(); + p_script->instances.insert(p_owner); - if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it - if (get(state_pair.first, state_pair.second)) { - r_state.push_back(state_pair); - } - } - } + return instance; } -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); +Object *CSharpInstance::get_owner() { + return owner; +} - for (const KeyValue<StringName, CSharpScript::EventSignal> &E : script->event_signals) { - const CSharpScript::EventSignal &event_signal = E.value; +bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { + ERR_FAIL_COND_V(!script.is_valid(), false); - MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed); - if (!delegate_field_value) { - continue; // Empty - } + return GDMonoCache::managed_callbacks.CSharpInstanceBridge_Set( + gchandle.get_intptr(), &p_name, &p_value); +} - Array serialized_data; - MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); +bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V(!script.is_valid(), false); - MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate_field_value, managed_serialized_data, &exc); + Variant ret_value; - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - continue; - } + bool ret = GDMonoCache::managed_callbacks.CSharpInstanceBridge_Get( + gchandle.get_intptr(), &p_name, &ret_value); - 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"); - } + if (ret) { + r_ret = ret_value; + return true; } + + return false; } void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { @@ -1807,30 +1525,25 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { ERR_FAIL_COND(!script.is_valid()); - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - - GDMonoClass *top = script->script_class; + StringName method = SNAME("_get_property_list"); - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get_property_list), 0); + Variant ret; + Callable::CallError call_error; + bool ok = GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &method, nullptr, 0, &call_error, &ret); - if (method) { - MonoObject *ret = method->invoke(mono_object); - - if (ret) { - Array array = Array(GDMonoMarshal::mono_object_to_variant(ret)); - for (int i = 0, size = array.size(); i < size; i++) { - props.push_back(PropertyInfo::from_dict(array.get(i))); - } + // CALL_ERROR_INVALID_METHOD would simply mean it was not overridden + if (call_error.error != Callable::CallError::CALL_ERROR_INVALID_METHOD) { + if (call_error.error != Callable::CallError::CALL_OK) { + ERR_PRINT("Error calling '_get_property_list': " + Variant::get_call_error_text(method, nullptr, 0, call_error)); + } else if (!ok) { + ERR_PRINT("Unexpected error calling '_get_property_list'"); + } else { + Array array = ret; + for (int i = 0, size = array.size(); i < size; i++) { + p_properties->push_back(PropertyInfo::from_dict(array.get(i))); } - - break; } - - top = top->get_parent_class(); } for (const PropertyInfo &prop : props) { @@ -1853,84 +1566,79 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool * return Variant::NIL; } -void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { - if (!script->is_valid() || !script->script_class) { - return; - } - - GD_MONO_SCOPE_THREAD_ATTACH; - - // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. - GDMonoClass *top = script->script_class; +bool CSharpInstance::property_can_revert(const StringName &p_name) const { + ERR_FAIL_COND_V(!script.is_valid(), false); - while (top && top != script->native) { - const Vector<GDMonoMethod *> &methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); ++i) { - MethodInfo minfo = methods[i]->get_method_info(); - if (minfo.name != CACHED_STRING_NAME(dotctor)) { - p_list->push_back(minfo); - } - } + Variant name_arg = p_name; + const Variant *args[1] = { &name_arg }; - top = top->get_parent_class(); - } -} + Variant ret; + Callable::CallError call_error; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &CACHED_STRING_NAME(_property_can_revert), args, 1, &call_error, &ret); -bool CSharpInstance::has_method(const StringName &p_method) const { - if (!script.is_valid()) { + if (call_error.error != Callable::CallError::CALL_OK) { return false; } - GD_MONO_SCOPE_THREAD_ATTACH; + return (bool)ret; +} - GDMonoClass *top = script->script_class; +bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V(!script.is_valid(), false); - while (top && top != script->native) { - if (top->has_fetched_method_unknown_params(p_method)) { - return true; - } + Variant name_arg = p_name; + const Variant *args[1] = { &name_arg }; + + Variant ret; + Callable::CallError call_error; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &CACHED_STRING_NAME(_property_get_revert), args, 1, &call_error, &ret); - top = top->get_parent_class(); + if (call_error.error != Callable::CallError::CALL_OK) { + return false; } - return false; + r_ret = ret; + return true; } -Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - ERR_FAIL_COND_V(!script.is_valid(), Variant()); - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - - if (!mono_object) { - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V(Variant()); +void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { + if (!script->is_valid() || !script->valid) { + return; } - GDMonoClass *top = script->script_class; + const CSharpScript *top = script.ptr(); + while (top != nullptr) { + for (const CSharpScript::CSharpMethodInfo &E : top->methods) { + p_list->push_back(E.method_info); + } - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(p_method, p_argcount); + top = top->base_script.ptr(); + } +} - if (method) { - MonoObject *return_value = method->invoke(mono_object, p_args); +bool CSharpInstance::has_method(const StringName &p_method) const { + if (!script.is_valid()) { + return false; + } - r_error.error = Callable::CallError::CALL_OK; + if (!GDMonoCache::godot_api_cache_updated) { + return false; + } - if (return_value) { - return GDMonoMarshal::mono_object_to_variant(return_value); - } else { - return Variant(); - } - } + return GDMonoCache::managed_callbacks.CSharpInstanceBridge_HasMethodUnknownParams( + gchandle.get_intptr(), &p_method); +} - top = top->get_parent_class(); - } +Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + ERR_FAIL_COND_V(!script.is_valid(), Variant()); - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + Variant ret; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &p_method, p_args, p_argcount, &r_error, &ret); - return Variant(); + return ret; } bool CSharpInstance::_reference_owner_unsafe() { @@ -1976,48 +1684,29 @@ bool CSharpInstance::_unreference_owner_unsafe() { return static_cast<RefCounted *>(owner)->unreference(); } -MonoObject *CSharpInstance::_internal_new_managed() { - // 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, nullptr, - "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'."); - +bool CSharpInstance::_internal_new_managed() { CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); - ERR_FAIL_NULL_V(owner, nullptr); - ERR_FAIL_COND_V(script.is_null(), nullptr); + ERR_FAIL_NULL_V(owner, false); + ERR_FAIL_COND_V(script.is_null(), false); - MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr()); + bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance( + script.ptr(), owner, nullptr, 0); - if (!mono_object) { + if (!ok) { // Important to clear this before destroying the script instance here script = Ref<CSharpScript>(); - - bool die = _unreference_owner_unsafe(); - // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. - CRASH_COND(die); - owner = nullptr; - ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); - } - - // Tie managed to unmanaged - gchandle = MonoGCHandleData::new_strong_handle(mono_object); - - if (base_ref_counted) { - _reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) + return false; } - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner); - - // Construct - ctor->invoke_raw(mono_object, nullptr); + CRASH_COND(gchandle.is_released()); - return mono_object; + return true; } -void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { +void CSharpInstance::mono_object_disposed(GCHandleIntPtr p_gchandle_to_free) { // Must make sure event signals are not left dangling disconnect_event_signals(); @@ -2025,10 +1714,10 @@ void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { CRASH_COND(base_ref_counted); CRASH_COND(gchandle.is_released()); #endif - CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + CSharpLanguage::get_singleton()->release_script_gchandle_thread_safe(p_gchandle_to_free, gchandle); } -void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) { +void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref_counted); CRASH_COND(gchandle.is_released()); @@ -2044,20 +1733,20 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f r_delete_owner = true; } else { r_delete_owner = false; - CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + CSharpLanguage::get_singleton()->release_script_gchandle_thread_safe(p_gchandle_to_free, gchandle); if (!p_is_finalizer) { // If the native instance is still alive and Dispose() was called // (instead of the finalizer), then we remove the script instance. r_remove_script_instance = true; + // TODO: Last usage of 'is_finalizing_scripts_domain'. It should be replaced with a check to determine if the load context is being unloaded. } else if (!GDMono::get_singleton()->is_finalizing_scripts_domain()) { // If the native instance is still alive and this is called from the finalizer, // then it was referenced from another thread before the finalizer could // unreference and delete it, so we want to keep it. // GC.ReRegisterForFinalize(this) is not safe because the objects referenced by 'this' // could have already been collected. Instead we will create a new managed instance here. - MonoObject *new_managed = _internal_new_managed(); - if (!new_managed) { + if (!_internal_new_managed()) { r_remove_script_instance = true; } } @@ -2065,17 +1754,20 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f } void CSharpInstance::connect_event_signals() { - for (const KeyValue<StringName, CSharpScript::EventSignal> &E : script->event_signals) { - const CSharpScript::EventSignal &event_signal = E.value; + CSharpScript *top = script.ptr(); + while (top != nullptr) { + for (CSharpScript::EventSignalInfo &signal : top->get_script_event_signals()) { + String signal_name = signal.name; - StringName signal_name = event_signal.field->get_name(); + // TODO: Use pooling for ManagedCallable instances. + EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name)); - // TODO: Use pooling for ManagedCallable instances. - EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(signal_name, callable); + } - Callable callable(event_signal_callable); - connected_event_signals.push_back(callable); - owner->connect(signal_name, callable); + top = top->base_script.ptr(); } } @@ -2097,16 +1789,25 @@ void CSharpInstance::refcount_incremented() { RefCounted *rc_owner = Object::cast_to<RefCounted>(owner); if (rc_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. // Release the current weak handle and replace it with a strong handle. - MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(gchandle.get_target()); - gchandle.release(); - gchandle = strong_gchandle; + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = false; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { + return; // Called after the managed side was collected, so nothing to do here + } + + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE); } } @@ -2121,15 +1822,24 @@ bool CSharpInstance::refcount_decremented() { int refcount = rc_owner->reference_get_count(); 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. - MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(gchandle.get_target()); - gchandle.release(); - gchandle = weak_gchandle; + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = true; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { + return refcount == 0; // Called after the managed side was collected, so nothing to do here + } + + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE); return false; } @@ -2144,8 +1854,6 @@ const Variant CSharpInstance::get_rpc_config() const { } void CSharpInstance::notification(int p_notification) { - GD_MONO_SCOPE_THREAD_ATTACH; - if (p_notification == Object::NOTIFICATION_PREDELETE) { // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed @@ -2165,15 +1873,8 @@ void CSharpInstance::notification(int p_notification) { _call_notification(p_notification); - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( + gchandle.get_intptr(), /* okIfNull */ false); return; } @@ -2182,62 +1883,29 @@ void CSharpInstance::notification(int p_notification) { } void CSharpInstance::_call_notification(int p_notification) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - - // Custom version of _call_multilevel, optimized for _notification + Variant arg = p_notification; + const Variant *args[1] = { &arg }; + StringName method_name = SNAME("_notification"); - int32_t arg = p_notification; - void *args[1] = { &arg }; - StringName method_name = CACHED_STRING_NAME(_notification); + Callable::CallError call_error; - GDMonoClass *top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(method_name, 1); - - if (method) { - method->invoke_raw(mono_object, args); - return; - } - - top = top->get_parent_class(); - } + Variant ret; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &method_name, args, 1, &call_error, &ret); } String CSharpInstance::to_string(bool *r_valid) { - GD_MONO_SCOPE_THREAD_ATTACH; + String res; + bool valid; - MonoObject *mono_object = get_mono_object(); + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallToString( + gchandle.get_intptr(), &res, &valid); - if (mono_object == nullptr) { - if (r_valid) { - *r_valid = false; - } - return String(); - } - - MonoException *exc = nullptr; - MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - if (r_valid) { - *r_valid = false; - } - return String(); - } - - if (result == nullptr) { - if (r_valid) { - *r_valid = false; - } - return String(); + if (r_valid) { + *r_valid = valid; } - return GDMonoMarshal::mono_string_to_godot(result); + return res; } Ref<Script> CSharpInstance::get_script() const { @@ -2253,8 +1921,6 @@ CSharpInstance::CSharpInstance(const Ref<CSharpScript> &p_script) : } CSharpInstance::~CSharpInstance() { - GD_MONO_SCOPE_THREAD_ATTACH; - destructing_script_instance = true; // Must make sure event signals are not left dangling @@ -2268,16 +1934,8 @@ CSharpInstance::~CSharpInstance() { // 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(); - - if (mono_object) { - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } - } + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( + gchandle.get_intptr(), /* okIfNull */ true); } gchandle.release(); // Make sure the gchandle is released @@ -2341,63 +1999,8 @@ void CSharpScript::_update_exports_values(HashMap<StringName, Variant> &values, propnames.push_back(prop_info); } - if (base_cache.is_valid()) { - base_cache->_update_exports_values(values, propnames); - } -} - -void CSharpScript::_update_member_info_no_exports() { - if (exports_invalidated) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - exports_invalidated = false; - - member_info.clear(); - - GDMonoClass *top = script_class; - List<PropertyInfo> props; - - while (top && top != native) { - PropertyInfo prop_info; - bool exported; - - const Vector<GDMonoField *> &fields = top->get_all_fields(); - - for (int i = fields.size() - 1; i >= 0; i--) { - GDMonoField *field = fields[i]; - - if (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) { - StringName member_name = field->get_name(); - - member_info[member_name] = prop_info; - props.push_front(prop_info); - exported_members_defval_cache[member_name] = Variant(); - } - } - - const Vector<GDMonoProperty *> &properties = top->get_all_properties(); - - for (int i = properties.size() - 1; i >= 0; i--) { - GDMonoProperty *property = properties[i]; - - if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) { - StringName member_name = property->get_name(); - - member_info[member_name] = prop_info; - props.push_front(prop_info); - exported_members_defval_cache[member_name] = Variant(); - } - } - - exported_members_cache.push_back(PropertyInfo(Variant::NIL, top->get_name(), PROPERTY_HINT_NONE, get_path(), PROPERTY_USAGE_CATEGORY)); - for (const PropertyInfo &E : props) { - exported_members_cache.push_back(E); - } - - props.clear(); - - top = top->get_parent_class(); - } + if (base_script.is_valid()) { + base_script->_update_exports_values(values, propnames); } } #endif @@ -2419,166 +2022,65 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda if (exports_invalidated) #endif { - GD_MONO_SCOPE_THREAD_ATTACH; +#ifdef TOOLS_ENABLED + exports_invalidated = false; +#endif changed = true; member_info.clear(); #ifdef TOOLS_ENABLED - MonoObject *tmp_object = nullptr; - Object *tmp_native = nullptr; - uint32_t tmp_pinned_gchandle = 0; - - if (is_editor) { - exports_invalidated = false; - - exported_members_cache.clear(); - exported_members_defval_cache.clear(); - - // Here we create a temporary managed instance of the class to get the initial values - tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); - - if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject."); - return false; - } - - 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, false, - "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); - - MonoException *ctor_exc = nullptr; - ctor->invoke(tmp_object, nullptr, &ctor_exc); - - 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? - - GDMonoUtils::free_gchandle(tmp_pinned_gchandle); - tmp_object = nullptr; - - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(ctor_exc); - return false; - } - } + exported_members_cache.clear(); + exported_members_defval_cache.clear(); #endif - GDMonoClass *top = script_class; - List<PropertyInfo> props; - - while (top && top != native) { - PropertyInfo prop_info; - bool exported; - - const Vector<GDMonoField *> &fields = top->get_all_fields(); - - for (int i = fields.size() - 1; i >= 0; i--) { - GDMonoField *field = fields[i]; - - if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) { - StringName member_name = field->get_name(); - - member_info[member_name] = prop_info; - - if (exported) { + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyInfoList(this, + [](CSharpScript *p_script, const String *p_current_class_name, GDMonoCache::godotsharp_property_info *p_props, int32_t p_count) { #ifdef TOOLS_ENABLED - if (is_editor) { - props.push_front(prop_info); - - if (tmp_object) { - exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); - } - } + p_script->exported_members_cache.push_back(PropertyInfo( + Variant::NIL, *p_current_class_name, PROPERTY_HINT_NONE, + p_script->get_path(), PROPERTY_USAGE_CATEGORY)); #endif -#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) - exported_members_names.insert(member_name); -#endif - } - } - } + for (int i = 0; i < p_count; i++) { + const GDMonoCache::godotsharp_property_info &prop = p_props[i]; - const Vector<GDMonoProperty *> &properties = top->get_all_properties(); + StringName name = *reinterpret_cast<const StringName *>(&prop.name); + String hint_string = *reinterpret_cast<const String *>(&prop.hint_string); - for (int i = properties.size() - 1; i >= 0; i--) { - GDMonoProperty *property = properties[i]; + PropertyInfo pinfo(prop.type, name, prop.hint, hint_string, prop.usage); - if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) { - StringName member_name = property->get_name(); + p_script->member_info[name] = pinfo; - member_info[member_name] = prop_info; + if (prop.exported) { - if (exported) { #ifdef TOOLS_ENABLED - if (is_editor) { - props.push_front(prop_info); - if (tmp_object) { - MonoException *exc = nullptr; - MonoObject *ret = property->get_value(tmp_object, &exc); - if (exc) { - exported_members_defval_cache[member_name] = Variant(); - GDMonoUtils::debug_print_unhandled_exception(exc); - } else { - exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); - } - } - } + p_script->exported_members_cache.push_back(pinfo); #endif #if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) - exported_members_names.insert(member_name); + p_script->exported_members_names.insert(name); #endif - } - } - } - -#ifdef TOOLS_ENABLED - exported_members_cache.push_back(PropertyInfo(Variant::NIL, top->get_name(), PROPERTY_HINT_NONE, get_path(), PROPERTY_USAGE_CATEGORY)); - - for (const PropertyInfo &E : props) { - exported_members_cache.push_back(E); - } - - props.clear(); -#endif // TOOLS_ENABLED - - top = top->get_parent_class(); - } + } + } + }); #ifdef TOOLS_ENABLED - if (is_editor) { - // Need to check this here, before disposal - bool base_ref_counted = Object::cast_to<RefCounted>(tmp_native) != nullptr; - - // Dispose the temporary managed instance + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyDefaultValues(this, + [](CSharpScript *p_script, GDMonoCache::godotsharp_property_def_val_pair *p_def_vals, int32_t p_count) { + for (int i = 0; i < p_count; i++) { + const GDMonoCache::godotsharp_property_def_val_pair &def_val_pair = p_def_vals[i]; - MonoException *exc = nullptr; - GDMonoUtils::dispose(tmp_object, &exc); + StringName name = *reinterpret_cast<const StringName *>(&def_val_pair.name); + Variant value = *reinterpret_cast<const Variant *>(&def_val_pair.value); - if (exc) { - ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(exc); - } - - GDMonoUtils::free_gchandle(tmp_pinned_gchandle); - tmp_object = nullptr; - - if (tmp_native && !base_ref_counted) { - Node *node = Object::cast_to<Node>(tmp_native); - if (node && node->is_inside_tree()) { - ERR_PRINT("Temporary instance was added to the scene tree."); - } else { - memdelete(tmp_native); - } - } - } + p_script->exported_members_defval_cache[name] = value; + } + }); #endif + } } #ifdef TOOLS_ENABLED @@ -2605,374 +2107,6 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda return changed; } -void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) { - // no need to load the script's signals more than once - if (!signals_invalidated) { - return; - } - - // make sure this classes signals are empty when loading for the first time - _signals.clear(); - event_signals.clear(); - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = p_class; - while (top && top != p_native_class) { - const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); - for (int i = delegates.size() - 1; i >= 0; --i) { - GDMonoClass *delegate = delegates[i]; - - 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())) { - String event_name = String::utf8(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, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - Vector<StringName> names; - Vector<ManagedType> types; - p_delegate_invoke->get_parameter_names(names); - p_delegate_invoke->get_parameter_types(types); - - for (int i = 0; i < names.size(); ++i) { - SignalParameter arg; - arg.name = names[i]; - - bool nil_is_variant = false; - arg.type = GDMonoMarshal::managed_to_variant_type(types[i], &nil_is_variant); - - 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 true; -} - -/** - * Returns false if there was an error, otherwise true. - * If there was an error, r_prop_info and r_exported are not assigned any value. - */ -bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - // Goddammit, C++. All I wanted was some nested functions. -#define MEMBER_FULL_QUALIFIED_NAME(m_member) \ - (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) - - if (p_member->is_static()) { -#ifdef TOOLS_ENABLED - if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) { - ERR_PRINT("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - } -#endif - return false; - } - - if (member_info.has(p_member->get_name())) { - return false; - } - - ManagedType type; - - if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_FIELD) { - type = static_cast<GDMonoField *>(p_member)->get_type(); - } else if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { - type = static_cast<GDMonoProperty *>(p_member)->get_type(); - } else { - CRASH_NOW(); - } - - bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute)); - - if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { - GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); - if (!property->has_getter()) { -#ifdef TOOLS_ENABLED - if (exported) { - ERR_PRINT("Cannot export a property without a getter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - } -#endif - return false; - } - if (!property->has_setter()) { -#ifdef TOOLS_ENABLED - if (exported) { - ERR_PRINT("Cannot export a property without a setter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - } -#endif - return false; - } - } - - 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); - r_exported = false; - return true; - } - -#ifdef TOOLS_ENABLED - MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); -#endif - - PropertyHint hint = PROPERTY_HINT_NONE; - String hint_string; - - if (variant_type == Variant::NIL && !nil_is_variant) { -#ifdef TOOLS_ENABLED - ERR_PRINT("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); -#endif - return false; - } - -#ifdef TOOLS_ENABLED - int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); - - ERR_FAIL_COND_V_MSG(hint_res == -1, false, - "Error while trying to determine information about the exported member: '" + - MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - - if (hint_res == 0) { - hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); - hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); - } -#endif - - 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; - -#undef MEMBER_FULL_QUALIFIED_NAME -} - -#ifdef TOOLS_ENABLED -int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { - if (p_variant_type == Variant::NIL) { - // System.Object (Variant) - return 1; - } - - 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())) { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); - r_hint = GDMonoUtils::Marshal::type_has_flags_attribute(reftype) ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; - - Vector<MonoClassField *> fields = p_type.type_class->get_enum_fields(); - - MonoType *enum_basetype = mono_class_enum_basetype(p_type.type_class->get_mono_ptr()); - - String name_only_hint_string; - - // True: enum Foo { Bar, Baz, Quux } - // True: enum Foo { Bar = 0, Baz = 1, Quux = 2 } - // False: enum Foo { Bar = 0, Baz = 7, Quux = 5 } - bool uses_default_values = true; - - for (int i = 0; i < fields.size(); i++) { - MonoClassField *field = fields[i]; - - if (i > 0) { - r_hint_string += ","; - name_only_hint_string += ","; - } - - String enum_field_name = String::utf8(mono_field_get_name(field)); - r_hint_string += enum_field_name; - name_only_hint_string += enum_field_name; - - // TODO: - // 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, nullptr); - - ERR_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value."); - - bool r_error; - uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error); - ERR_FAIL_COND_V_MSG(r_error, -1, "Failed to unbox '" + enum_field_name + "' constant enum value."); - - unsigned int expected_val = r_hint == PROPERTY_HINT_FLAGS ? 1 << i : i; - if (val != expected_val) { - uses_default_values = false; - } - - r_hint_string += ":"; - r_hint_string += String::num_uint64(val); - } - - if (uses_default_values) { - // If we use the format NAME:VAL, that's what the editor displays. - // That's annoying if the user is not using custom values for the enum constants. - // This may not be needed in the future if the editor is changed to not display values. - r_hint_string = name_only_hint_string; - } - } 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 == nullptr); - - r_hint = PROPERTY_HINT_RESOURCE_TYPE; - r_hint_string = String(NATIVE_GDMONOCLASS_NAME(field_native_class)); - } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(Node)->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 == nullptr); - - r_hint = PROPERTY_HINT_NODE_TYPE; - 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 - - ManagedType elem_type; - - if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type)) { - return 0; - } - - Variant::Type elem_variant_type = GDMonoMarshal::managed_to_variant_type(elem_type); - - PropertyHint elem_hint = PROPERTY_HINT_NONE; - String elem_hint_string; - - ERR_FAIL_COND_V_MSG(elem_variant_type == Variant::NIL, -1, "Unknown array element type."); - - bool preset_hint = false; - if (elem_variant_type == Variant::STRING) { - MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); - if (PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)) == PROPERTY_HINT_ENUM) { - r_hint_string = itos(elem_variant_type) + "/" + itos(PROPERTY_HINT_ENUM) + ":" + CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); - preset_hint = true; - } - } - - if (!preset_hint) { - int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string); - - ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type."); - - // Format: type/hint:hint_string - r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string; - } - - r_hint = PROPERTY_HINT_TYPE_STRING; - - } else if (p_allow_generics && p_variant_type == Variant::DICTIONARY) { - // TODO: Dictionaries are not supported in the inspector - } else { - return 0; - } - - return 1; -} -#endif - -Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (unlikely(GDMono::get_singleton() == nullptr)) { - // Probably not the best error but eh. - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - return Variant(); - } - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = script_class; - - while (top && top != native) { - GDMonoMethod *method = top->get_method(p_method, p_argcount); - - if (method && method->is_static()) { - MonoObject *result = method->invoke(nullptr, p_args); - - if (result) { - return GDMonoMarshal::mono_object_to_variant(result); - } else { - return Variant(); - } - } - - top = top->get_parent_class(); - } - - // No static method found. Try regular instance calls - return Script::callp(p_method, p_args, p_argcount, r_error); -} - -void CSharpScript::_resource_path_changed() { - _update_name(); -} - bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == CSharpLanguage::singleton->string_names._script_source) { r_ret = get_source_code(); @@ -3000,107 +2134,107 @@ void CSharpScript::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new")); } -Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail, only assertions allowed - - CRASH_COND(p_class == nullptr); +void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { + // IMPORTANT: + // This method must be called only after the CSharpScript and its associated type + // have been added to the script bridge map in the ScriptManagerBridge C# class. + // Other than that, it's the same as `CSharpScript::reload`. - // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time - Ref<CSharpScript> script = memnew(CSharpScript); + // This method should not fail, only assertions allowed. - initialize_for_managed_type(script, p_class, p_native); - - return script; -} - -void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail, only assertions allowed - - 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 == nullptr); + // Unlike `reload`, we print an error rather than silently returning, + // as we can assert this won't be called a second time until invalidated. + ERR_FAIL_COND(!p_script->reload_invalidated); p_script->valid = true; p_script->reload_invalidated = false; update_script_class_info(p_script); -#ifdef TOOLS_ENABLED - p_script->_update_member_info_no_exports(); -#endif + p_script->_update_exports(); } // Extract information about the script using the mono class. void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { - GDMonoClass *base = p_script->script_class->get_parent_class(); + bool tool = false; - // `base` should only be set if the script is a user defined type. - if (base != p_script->native) { - p_script->base = base; - } + // TODO: Use GDNative godot_dictionary + Array methods_array; + methods_array.~Array(); + Dictionary rpc_functions_dict; + rpc_functions_dict.~Dictionary(); + Dictionary signals_dict; + signals_dict.~Dictionary(); - p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + Ref<CSharpScript> base_script; + GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo( + p_script.ptr(), &tool, &methods_array, &rpc_functions_dict, &signals_dict, &base_script); - if (!p_script->tool) { - GDMonoClass *nesting_class = p_script->script_class->get_nesting_class(); - p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); - } + p_script->tool = tool; -#ifdef TOOLS_ENABLED - if (!p_script->tool) { - p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); - } -#endif + p_script->rpc_config.clear(); + p_script->rpc_config = rpc_functions_dict; -#ifdef DEBUG_ENABLED - // For debug builds, we must fetch from all native base methods as well. - // Native base methods must be fetched before the current class. - // Not needed if the script class itself is a native class. + // Methods - if (p_script->script_class != p_script->native) { - GDMonoClass *native_top = p_script->native; - while (native_top) { - native_top->fetch_methods_with_godot_api_checks(p_script->native); + p_script->methods.clear(); - if (native_top == CACHED_CLASS(GodotObject)) { - break; - } + p_script->methods.resize(methods_array.size()); + int push_index = 0; + + for (int i = 0; i < methods_array.size(); i++) { + Dictionary method_info_dict = methods_array[i]; - native_top = native_top->get_parent_class(); + StringName name = method_info_dict["name"]; + + MethodInfo mi; + mi.name = name; + + Array params = method_info_dict["params"]; + + for (int j = 0; j < params.size(); j++) { + Dictionary param = params[j]; + + Variant::Type param_type = (Variant::Type)(int)param["type"]; + PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]); + arg_info.usage = (uint32_t)param["usage"]; + mi.arguments.push_back(arg_info); } + + p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi }); } -#endif - p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native); + // Event signals - p_script->rpc_config.clear(); + // Performance is not critical here as this will be replaced with source generators. - GDMonoClass *top = p_script->script_class; - while (top && top != p_script->native) { - // Fetch methods from base classes as well - top->fetch_methods_with_godot_api_checks(p_script->native); + p_script->event_signals.clear(); - // Update RPC info - { - Vector<GDMonoMethod *> methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); i++) { - if (!methods[i]->is_static()) { - const Variant rpc_config = p_script->_member_get_rpc_config(methods[i]); - if (rpc_config.get_type() != Variant::NIL) { - p_script->rpc_config[methods[i]->get_name()] = rpc_config; - } - } - } + // Sigh... can't we just have capacity? + p_script->event_signals.resize(signals_dict.size()); + push_index = 0; + + for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) { + StringName name = *s; + + MethodInfo mi; + mi.name = name; + + Array params = signals_dict[*s]; + + for (int i = 0; i < params.size(); i++) { + Dictionary param = params[i]; + + Variant::Type param_type = (Variant::Type)(int)param["type"]; + PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]); + arg_info.usage = (uint32_t)param["usage"]; + mi.arguments.push_back(arg_info); } - top = top->get_parent_class(); + p_script->event_signals.set(push_index++, EventSignalInfo{ name, mi }); } - p_script->load_script_signals(p_script->script_class, p_script->native); + p_script->base_script = base_script; } bool CSharpScript::can_instantiate() const { @@ -3113,43 +2247,22 @@ bool CSharpScript::can_instantiate() const { // FIXME Need to think this through better. // For tool scripts, this will never fire if the class is not found. That's because we // don't know if it's a tool script if we can't find the class to access the attributes. - if (extra_cond && !script_class) { - if (GDMono::get_singleton()->get_project_assembly() == nullptr) { - // The project assembly is not loaded - 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(false, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); - } + if (extra_cond && !valid) { + ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'."); } return valid && extra_cond; } StringName CSharpScript::get_instance_base_type() const { - if (native) { - return native->get_name(); - } else { - return StringName(); - } + StringName native_name; + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name); + return native_name; } CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { - GD_MONO_ASSERT_THREAD_ATTACHED; - /* STEP 1, CREATE */ - // Search the constructor first, to fail with an error if it's not found before allocating anything else. - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); - if (ctor == 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().is_empty() ? String() : " Path: '" + get_path() + "'.")); - - ERR_FAIL_V_MSG(nullptr, "Constructor not found."); - } - Ref<RefCounted> ref; if (p_is_ref_counted) { // Hold it alive. Important if we have to dispose a script instance binding before creating the CSharpInstance. @@ -3163,15 +2276,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); if (script_binding.inited && !script_binding.gchandle.is_released()) { - MonoObject *mono_object = script_binding.gchandle.get_target(); - if (mono_object) { - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } - } + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( + script_binding.gchandle.get_intptr(), /* okIfNull */ true); script_binding.gchandle.release(); // Just in case script_binding.inited = false; @@ -3185,38 +2291,19 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg /* STEP 2, INITIALIZE AND CONSTRUCT */ - MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance( + this, p_owner, p_args, p_argcount); - if (!mono_object) { + if (!ok) { // Important to clear this before destroying the script instance here instance->script = Ref<CSharpScript>(); 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); - p_owner->set_script_instance(nullptr); - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); - } - - // Tie managed to unmanaged - instance->gchandle = MonoGCHandleData::new_strong_handle(mono_object); - if (instance->base_ref_counted) { - instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) - } - - { - MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); - instances.insert(instance->owner); + return nullptr; } - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner); - - // Construct - ctor->invoke(mono_object, p_args); + CRASH_COND(instance->gchandle.is_released()); /* STEP 3, PARTY */ @@ -3232,11 +2319,12 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal r_error.error = Callable::CallError::CALL_OK; - ERR_FAIL_NULL_V(native, Variant()); + StringName native_name; + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name); - GD_MONO_SCOPE_THREAD_ATTACH; + ERR_FAIL_COND_V(native_name == StringName(), Variant()); - Object *owner = ClassDB::instantiate(NATIVE_GDMONOCLASS_NAME(native)); + Object *owner = ClassDB::instantiate(native_name); Ref<RefCounted> ref; RefCounted *r = Object::cast_to<RefCounted>(owner); @@ -3264,18 +2352,18 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { CRASH_COND(!valid); #endif - GD_MONO_SCOPE_THREAD_ATTACH; + StringName native_name; + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name); - if (native) { - StringName native_name = NATIVE_GDMONOCLASS_NAME(native); - if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { - 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 instantiated 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 instantiated in object of type: '" + p_this->get_class() + "'."); + ERR_FAIL_COND_V(native_name == StringName(), nullptr); + + if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { + 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 instantiated 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 instantiated in object of type: '" + p_this->get_class() + "'."); } Callable::CallError unchecked_error; @@ -3317,54 +2405,43 @@ void CSharpScript::set_source_code(const String &p_code) { } void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { - if (!script_class) { + if (!valid) { return; } - GD_MONO_SCOPE_THREAD_ATTACH; - - // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. - GDMonoClass *top = script_class; - - while (top && top != native) { - const Vector<GDMonoMethod *> &methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); ++i) { - MethodInfo minfo = methods[i]->get_method_info(); - if (minfo.name != CACHED_STRING_NAME(dotctor)) { - p_list->push_back(methods[i]->get_method_info()); - } + const CSharpScript *top = this; + while (top != nullptr) { + for (const CSharpMethodInfo &E : top->methods) { + p_list->push_back(E.method_info); } - top = top->get_parent_class(); + top = top->base_script.ptr(); } } bool CSharpScript::has_method(const StringName &p_method) const { - if (!script_class) { + if (!valid) { return false; } - GD_MONO_SCOPE_THREAD_ATTACH; + for (const CSharpMethodInfo &E : methods) { + if (E.name == p_method) { + return true; + } + } - return script_class->has_fetched_method_unknown_params(p_method); + return false; } MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { - if (!script_class) { + if (!valid) { return MethodInfo(); } - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = script_class; - - while (top && top != native) { - GDMonoMethod *params = top->get_fetched_method_unknown_params(p_method); - if (params) { - return params->get_method_info(); + for (const CSharpMethodInfo &E : methods) { + if (E.name == p_method) { + return E.method_info; } - - top = top->get_parent_class(); } return MethodInfo(); @@ -3379,30 +2456,15 @@ Error CSharpScript::reload(bool p_keep_state) { // That's done separately via domain reloading. reload_invalidated = false; - GD_MONO_SCOPE_THREAD_ATTACH; - - const DotNetScriptLookupInfo *lookup_info = - CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path()); - - if (lookup_info) { - GDMonoClass *klass = lookup_info->script_class; - if (klass) { - ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED); - script_class = klass; - } - } + String script_path = get_path(); - valid = script_class != nullptr; + valid = GDMonoCache::managed_callbacks.ScriptManagerBridge_AddScriptBridge(this, &script_path); - if (script_class) { + if (valid) { #ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); + print_verbose("Found class for script " + get_path()); #endif - native = GDMonoUtils::get_class_native_base(script_class); - - CRASH_COND(native == nullptr); - update_script_class_info(this); _update_exports(); @@ -3424,8 +2486,8 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari return true; } - if (base_cache.is_valid()) { - return base_cache->get_property_default_value(p_property, r_value); + if (base_script.is_valid()) { + return base_script->get_property_default_value(p_property, r_value); } #endif @@ -3439,48 +2501,39 @@ void CSharpScript::update_exports() { } bool CSharpScript::has_script_signal(const StringName &p_signal) const { - return event_signals.has(p_signal) || _signals.has(p_signal); -} - -void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { - for (const KeyValue<StringName, Vector<SignalParameter>> &E : _signals) { - 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]; + if (!valid) { + return false; + } - 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; - } + if (!GDMonoCache::godot_api_cache_updated) { + return false; + } - mi.arguments.push_back(arg_info); + for (const EventSignalInfo &signal : event_signals) { + if (signal.name == p_signal) { + return true; } - - r_signals->push_back(mi); } - for (const KeyValue<StringName, EventSignal> &E : event_signals) { - MethodInfo mi; - mi.name = E.key; - - 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]; + return false; +} - 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; - } +void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + if (!valid) { + return; + } - mi.arguments.push_back(arg_info); - } + for (const EventSignalInfo &signal : get_script_event_signals()) { + r_signals->push_back(signal.method_info); + } +} - r_signals->push_back(mi); +Vector<CSharpScript::EventSignalInfo> CSharpScript::get_script_event_signals() const { + if (!valid) { + return Vector<EventSignalInfo>(); } + + return event_signals; } bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { @@ -3489,38 +2542,47 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { return false; } - if (script_class == nullptr || cs->script_class == nullptr) { + if (!valid || !cs->valid) { return false; } - if (script_class == cs->script_class) { - return true; + if (!GDMonoCache::godot_api_cache_updated) { + return false; } - return cs->script_class->is_assignable_from(script_class); + return GDMonoCache::managed_callbacks.ScriptManagerBridge_ScriptIsOrInherits(this, cs.ptr()); } Ref<Script> CSharpScript::get_base_script() const { - // TODO search in metadata file once we have it, not important any way? - return Ref<Script>(); + return base_script; } void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const { - List<PropertyInfo> props; - #ifdef TOOLS_ENABLED - for (const PropertyInfo &E : exported_members_cache) { - props.push_back(E); + const CSharpScript *top = this; + while (top != nullptr) { + for (const PropertyInfo &E : top->exported_members_cache) { + r_list->push_back(E); + } + + top = top->base_script.ptr(); } #else - for (const KeyValue<StringName, PropertyInfo> &E : member_info) { - props.push_front(E.value); - } -#endif // TOOLS_ENABLED + const CSharpScript *top = this; + while (top != nullptr) { + List<PropertyInfo> props; - for (const PropertyInfo &prop : props) { - r_list->push_back(prop); + for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) { + props.push_front(E.value); + } + + for (const PropertyInfo &prop : props) { + r_list->push_back(prop); + } + + top = top->base_script.ptr(); } +#endif } int CSharpScript::get_member_line(const StringName &p_member) const { @@ -3528,22 +2590,6 @@ int CSharpScript::get_member_line(const StringName &p_member) const { return -1; } -Variant CSharpScript::_member_get_rpc_config(IMonoClassMember *p_member) const { - Variant out; - - MonoObject *rpc_attribute = p_member->get_attribute(CACHED_CLASS(RPCAttribute)); - if (rpc_attribute != nullptr) { - Dictionary rpc_config; - rpc_config["rpc_mode"] = CACHED_PROPERTY(RPCAttribute, Mode)->get_int_value(rpc_attribute); - rpc_config["call_local"] = CACHED_PROPERTY(RPCAttribute, CallLocal)->get_bool_value(rpc_attribute); - rpc_config["transfer_mode"] = CACHED_PROPERTY(RPCAttribute, TransferMode)->get_int_value(rpc_attribute); - rpc_config["channel"] = CACHED_PROPERTY(RPCAttribute, TransferChannel)->get_int_value(rpc_attribute); - out = rpc_config; - } - - return out; -} - const Variant CSharpScript::get_rpc_config() const { return rpc_config; } @@ -3564,29 +2610,15 @@ Error CSharpScript::load_source_code(const String &p_path) { return OK; } -void CSharpScript::_update_name() { - String path = get_path(); - - if (!path.is_empty()) { - name = get_path().get_file().get_basename(); - } -} - void CSharpScript::_clear() { tool = false; valid = false; reload_invalidated = true; - - base = nullptr; - native = nullptr; - script_class = nullptr; } CSharpScript::CSharpScript() { _clear(); - _update_name(); - #ifdef DEBUG_ENABLED { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); @@ -3600,6 +2632,10 @@ CSharpScript::~CSharpScript() { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); CSharpLanguage::get_singleton()->script_list.remove(&this->script_list); #endif + + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_RemoveScriptBridge(this); + } } void CSharpScript::get_members(HashSet<StringName> *p_members) { @@ -3621,9 +2657,13 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const // TODO ignore anything inside bin/ and obj/ in tools builds? - CSharpScript *script = memnew(CSharpScript); + Ref<CSharpScript> script; - Ref<CSharpScript> scriptres(script); + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &script); + } else { + script = Ref<CSharpScript>(memnew(CSharpScript)); + } #if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED) Error err = script->load_source_code(p_path); @@ -3638,7 +2678,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const *r_error = OK; } - return scriptres; + return script; } void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const { @@ -3701,14 +2741,7 @@ bool ResourceFormatSaverCSharpScript::recognize(const Ref<Resource> &p_resource) } CSharpLanguage::StringNameCache::StringNameCache() { - _signal_callback = StaticCString::create("_signal_callback"); - _set = StaticCString::create("_set"); - _get = StaticCString::create("_get"); - _get_property_list = StaticCString::create("_get_property_list"); - _notification = StaticCString::create("_notification"); + _property_can_revert = StaticCString::create("_property_can_revert"); + _property_get_revert = StaticCString::create("_property_get_revert"); _script_source = StaticCString::create("script/source"); - 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 48129e69cb..3509a5c87d 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -39,8 +39,6 @@ #include "mono_gc_handle.h" #include "mono_gd/gd_mono.h" -#include "mono_gd/gd_mono_header.h" -#include "mono_gd/gd_mono_internals.h" #ifdef TOOLS_ENABLED #include "editor/editor_plugin.h" @@ -67,48 +65,17 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { #define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst)) -struct DotNetScriptLookupInfo { - String class_namespace; - String class_name; - GDMonoClass *script_class = nullptr; - - DotNetScriptLookupInfo() {} // Required by HashMap... - - DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) : - class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) { - } -}; - 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 = false; bool valid = false; bool reload_invalidated = false; - GDMonoClass *base = nullptr; - GDMonoClass *native = nullptr; - GDMonoClass *script_class = nullptr; - - Ref<CSharpScript> base_cache; // TODO what's this for? + Ref<CSharpScript> base_script; HashSet<Object *> instances; @@ -118,26 +85,32 @@ private: // 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, Array>> event_signals; + Dictionary event_signals; }; HashSet<ObjectID> pending_reload_instances; RBMap<ObjectID, StateBackup> pending_reload_state; - StringName tied_class_name_for_reload; - StringName tied_class_namespace_for_reload; #endif String source; - StringName name; SelfList<CSharpScript> script_list = this; - HashMap<StringName, Vector<SignalParameter>> _signals; - HashMap<StringName, EventSignal> event_signals; - bool signals_invalidated = true; - Dictionary rpc_config; + struct EventSignalInfo { + StringName name; // MethodInfo stores a string... + MethodInfo method_info; + }; + + struct CSharpMethodInfo { + StringName name; // MethodInfo stores a string... + MethodInfo method_info; + }; + + Vector<EventSignalInfo> event_signals; + Vector<CSharpMethodInfo> methods; + #ifdef TOOLS_ENABLED List<PropertyInfo> exported_members_cache; // members_cache HashMap<StringName, Variant> exported_members_defval_cache; // member_default_values_cache @@ -146,7 +119,6 @@ private: bool placeholder_fallback_enabled = false; bool exports_invalidated = true; void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames); - void _update_member_info_no_exports(); void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override; #endif @@ -158,39 +130,24 @@ private: void _clear(); - void _update_name(); - - void load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class); - bool _get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms); - bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr); - bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported); -#ifdef TOOLS_ENABLED - static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string); -#endif - CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error); // Do not use unless you know what you are doing - friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); - static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native); static void update_script_class_info(Ref<CSharpScript> p_script); - static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native); - - Variant _member_get_rpc_config(IMonoClassMember *p_member) const; protected: static void _bind_methods(); - Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; - void _resource_path_changed() override; bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); void _get_property_list(List<PropertyInfo> *p_properties) const; public: + static void reload_registered_script(Ref<CSharpScript> p_script); + bool can_instantiate() const override; StringName get_instance_base_type() const override; ScriptInstance *instance_create(Object *p_this) override; @@ -214,14 +171,20 @@ public: bool has_script_signal(const StringName &p_signal) const override; void get_script_signal_list(List<MethodInfo> *r_signals) const override; + Vector<EventSignalInfo> get_script_event_signals() const; + bool get_property_default_value(const StringName &p_property, Variant &r_value) const override; void get_script_property_list(List<PropertyInfo> *r_list) const override; void update_exports() override; void get_members(HashSet<StringName> *p_members) override; - bool is_tool() const override { return tool; } - bool is_valid() const override { return valid; } + bool is_tool() const override { + return tool; + } + bool is_valid() const override { + return valid; + } bool inherits_script(const Ref<Script> &p_script) const override; @@ -237,7 +200,9 @@ public: const Variant get_rpc_config() const override; #ifdef TOOLS_ENABLED - bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } + bool is_placeholder_fallback_enabled() const override { + return placeholder_fallback_enabled; + } #endif Error load_source_code(const String &p_path); @@ -270,22 +235,18 @@ class CSharpInstance : public ScriptInstance { bool _unreference_owner_unsafe(); /* - * If nullptr is returned, the caller must destroy the script instance by removing it from its owner. + * If false is returned, the caller must destroy the script instance by removing it from its owner. */ - MonoObject *_internal_new_managed(); + bool _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 MonoGCHandleData &p_gchandle); - 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; - _FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; } + _FORCE_INLINE_ GCHandleIntPtr get_gchandle_intptr() { return gchandle.get_intptr(); } + Object *get_owner() override; bool set(const StringName &p_name, const Variant &p_value) override; @@ -293,17 +254,20 @@ public: void get_property_list(List<PropertyInfo> *p_properties) const override; Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override; + bool property_can_revert(const StringName &p_name) const override; + bool property_get_revert(const StringName &p_name, Variant &r_ret) const override; + void get_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; - void mono_object_disposed(MonoObject *p_obj); + void mono_object_disposed(GCHandleIntPtr p_gchandle_to_free); /* * If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, if * 'r_remove_script_instance' is set to true, the caller must destroy the script instance by removing it from its owner. */ - void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance); + void mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance); void connect_event_signals(); void disconnect_event_signals(); @@ -329,7 +293,6 @@ public: struct CSharpScriptBinding { bool inited = false; StringName type_name; - GDMonoClass *wrapper_class = nullptr; MonoGCHandleData gchandle; Object *owner = nullptr; @@ -367,33 +330,22 @@ class CSharpLanguage : public ScriptLanguage { ManagedCallableMiddleman *managed_callable_middleman = memnew(ManagedCallableMiddleman); struct StringNameCache { - StringName _signal_callback; - StringName _set; - StringName _get; - StringName _get_property_list; - StringName _notification; + StringName _property_can_revert; + StringName _property_get_revert; StringName _script_source; - StringName dotctor; // .ctor - StringName on_before_serialize; // OnBeforeSerialize - StringName on_after_deserialize; // OnAfterDeserialize - StringName delegate_invoke_method_name; StringNameCache(); }; int lang_idx = -1; - HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map; - - void lookup_script_for_class(GDMonoClass *p_class); - // For debug_break and debug_break_parse int _debug_parse_err_line = -1; String _debug_parse_err_file; String _debug_error; friend class GDMono; - void _on_scripts_domain_unloaded(); + void _on_scripts_domain_about_to_unload(); #ifdef TOOLS_ENABLED EditorPlugin *godotsharp_editor = nullptr; @@ -415,21 +367,35 @@ public: StringNameCache string_names; - const Mutex &get_language_bind_mutex() { return language_bind_mutex; } + const Mutex &get_language_bind_mutex() { + return language_bind_mutex; + } + const Mutex &get_script_instances_mutex() { + return script_instances_mutex; + } - _FORCE_INLINE_ int get_language_index() { return lang_idx; } + _FORCE_INLINE_ int get_language_index() { + return lang_idx; + } void set_language_index(int p_idx); - _FORCE_INLINE_ const StringNameCache &get_string_names() { return string_names; } + _FORCE_INLINE_ const StringNameCache &get_string_names() { + return string_names; + } - _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; } + _FORCE_INLINE_ static CSharpLanguage *get_singleton() { + return singleton; + } #ifdef TOOLS_ENABLED - _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; } + _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { + return godotsharp_editor; + } #endif static void release_script_gchandle(MonoGCHandleData &p_gchandle); - static void release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle); + static void release_script_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, MonoGCHandleData &r_gchandle); + static void release_binding_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, CSharpScriptBinding &r_script_binding); 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); @@ -439,12 +405,8 @@ public: void reload_assemblies(bool p_soft_reload); #endif - _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; } - - void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly); - - const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const { - return dotnet_script_lookup_map.getptr(p_script_path); + _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { + return managed_callable_middleman; } String get_name() const override; @@ -474,7 +436,9 @@ public: Script *create_script() const override; bool has_named_classes() const override; bool supports_builtin_mode() const override; - /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; } + /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { + return -1; + } String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override; virtual String _get_indentation() const; /* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {} @@ -489,14 +453,20 @@ public: /* TODO */ void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {} /* TODO */ void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {} /* TODO */ void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {} - /* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { return ""; } + /* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { + return ""; + } Vector<StackInfo> debug_get_current_stack_info() override; /* PROFILING FUNCTIONS */ /* TODO */ void profiling_start() override {} /* TODO */ void profiling_stop() override {} - /* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; } - /* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; } + /* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { + return 0; + } + /* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { + return 0; + } void frame() override; @@ -515,16 +485,12 @@ public: bool overrides_external_editor() override; #endif - /* THREAD ATTACHING */ - void thread_enter() override; - void thread_exit() override; - RBMap<Object *, CSharpScriptBinding>::Element *insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding); bool setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object); -#ifdef DEBUG_ENABLED - Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace); -#endif + static void tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted); + static void tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted); + static void tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged); void post_unsafe_reference(Object *p_obj); void pre_unsafe_unreference(Object *p_obj); diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml index b981542801..faf3512da7 100644 --- a/modules/mono/doc_classes/GodotSharp.xml +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -10,55 +10,10 @@ <tutorials> </tutorials> <methods> - <method name="attach_thread"> - <return type="void" /> - <description> - Attaches the current thread to the Mono runtime. - </description> - </method> - <method name="detach_thread"> - <return type="void" /> - <description> - Detaches the current thread from the Mono runtime. - </description> - </method> - <method name="get_domain_id"> - <return type="int" /> - <description> - Returns the current MonoDomain ID. - [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash. - </description> - </method> - <method name="get_scripts_domain_id"> - <return type="int" /> - <description> - Returns the scripts MonoDomain's ID. This will be the same MonoDomain ID as [method get_domain_id], unless the scripts domain isn't loaded. - [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash. - </description> - </method> - <method name="is_domain_finalizing_for_unload"> - <return type="bool" /> - <param index="0" name="domain_id" type="int" /> - <description> - Returns [code]true[/code] if the domain is being finalized, [code]false[/code] otherwise. - </description> - </method> <method name="is_runtime_initialized"> <return type="bool" /> <description> - Returns [code]true[/code] if the Mono runtime is initialized, [code]false[/code] otherwise. - </description> - </method> - <method name="is_runtime_shutting_down"> - <return type="bool" /> - <description> - Returns [code]true[/code] if the Mono runtime is shutting down, [code]false[/code] otherwise. - </description> - </method> - <method name="is_scripts_domain_loaded"> - <return type="bool" /> - <description> - Returns [code]true[/code] if the scripts domain is loaded, [code]false[/code] otherwise. + Returns [code]true[/code] if the .NET runtime is initialized, [code]false[/code] otherwise. </description> </method> </methods> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln index d1868f52ef..03a7dc453c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}" EndProject diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 4e9e7184da..013b210ff4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -26,16 +26,8 @@ <None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" /> <None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" /> <!-- SdkPackageVersions.props --> - <None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk"> + <None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk"> <Link>Sdk\SdkPackageVersions.props</Link> </None> </ItemGroup> - - <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> - <PropertyGroup> - <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> - <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> - </PropertyGroup> - <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> - </Target> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 5a499742e9..ad41ab04d5 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -77,7 +77,6 @@ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants> <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'linuxbsd' ">GODOT_LINUXBSD;GODOT_PC</GodotPlatformConstants> <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'macos' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants> - <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'server' ">GODOT_SERVER;GODOT_PC</GodotPlatformConstants> <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants> <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants> <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants> @@ -95,21 +94,4 @@ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> </PropertyGroup> - - <!-- Godot API references --> - <ItemGroup> - <!-- - TODO: - We should consider a nuget package for reference assemblies. This is difficult because the - Godot scripting API is continuaslly breaking backwards compatibility even in patch releases. - --> - <Reference Include="GodotSharp"> - <Private>false</Private> - <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath> - </Reference> - <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' "> - <Private>false</Private> - <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath> - </Reference> - </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index 397ede9644..aad4ea4553 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -19,4 +19,10 @@ <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' "> <PackageReference Include="Godot.SourceGenerators" Version="$(PackageFloatingVersion_Godot)" /> </ItemGroup> + + <!-- Godot API references --> + <ItemGroup Condition=" '$(DisableImplicitGodotSharpReferences)' != 'true' "> + <PackageReference Include="GodotSharp" Version="$(PackageVersion_GodotSharp)" /> + <PackageReference Include="GodotSharpEditor" Version="$(PackageVersion_GodotSharp)" Condition=" '$(Configuration)' == 'Debug' " /> + </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs new file mode 100644 index 0000000000..764ba8f121 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs @@ -0,0 +1,7 @@ +namespace Godot.SourceGenerators.Sample; + +public partial class EventSignals : Godot.Object +{ + [Signal] + public delegate void MySignalEventHandler(string str, int num); +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs new file mode 100644 index 0000000000..ac9f59aa99 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable CS0169 +#pragma warning disable CS0414 + +namespace Godot.SourceGenerators.Sample +{ + [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public partial class ExportedFields : Godot.Object + { + [Export] private Boolean field_Boolean = true; + [Export] private Char field_Char = 'f'; + [Export] private SByte field_SByte = 10; + [Export] private Int16 field_Int16 = 10; + [Export] private Int32 field_Int32 = 10; + [Export] private Int64 field_Int64 = 10; + [Export] private Byte field_Byte = 10; + [Export] private UInt16 field_UInt16 = 10; + [Export] private UInt32 field_UInt32 = 10; + [Export] private UInt64 field_UInt64 = 10; + [Export] private Single field_Single = 10; + [Export] private Double field_Double = 10; + [Export] private String field_String = "foo"; + + // Godot structs + [Export] private Vector2 field_Vector2 = new(10f, 10f); + [Export] private Vector2i field_Vector2i = Vector2i.Up; + [Export] private Rect2 field_Rect2 = new(new Vector2(10f, 10f), new Vector2(10f, 10f)); + [Export] private Rect2i field_Rect2i = new(new Vector2i(10, 10), new Vector2i(10, 10)); + [Export] private Transform2D field_Transform2D = Transform2D.Identity; + [Export] private Vector3 field_Vector3 = new(10f, 10f, 10f); + [Export] private Vector3i field_Vector3i = Vector3i.Back; + [Export] private Basis field_Basis = new Basis(Quaternion.Identity); + [Export] private Quaternion field_Quaternion = new Quaternion(Basis.Identity); + [Export] private Transform3D field_Transform3D = Transform3D.Identity; + [Export] private Vector4 field_Vector4 = new(10f, 10f, 10f, 10f); + [Export] private Vector4i field_Vector4i = Vector4i.One; + [Export] private Projection field_Projection = Projection.Identity; + [Export] private AABB field_AABB = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f)); + [Export] private Color field_Color = Colors.Aquamarine; + [Export] private Plane field_Plane = Plane.PlaneXZ; + [Export] private Callable field_Callable = new Callable(Engine.GetMainLoop(), "_process"); + [Export] private SignalInfo field_SignalInfo = new SignalInfo(Engine.GetMainLoop(), "property_list_changed"); + + // Enums + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyEnum + { + A, + B, + C + } + + [Export] private MyEnum field_Enum = MyEnum.C; + + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyFlagsEnum + { + A, + B, + C + } + + [Export] private MyFlagsEnum field_FlagsEnum = MyFlagsEnum.C; + + // Arrays + [Export] private Byte[] field_ByteArray = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int32[] field_Int32Array = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int64[] field_Int64Array = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Single[] field_SingleArray = { 0f, 1f, 2f, 3f, 4f, 5f, 6f }; + [Export] private Double[] field_DoubleArray = { 0d, 1d, 2d, 3d, 4d, 5d, 6d }; + [Export] private String[] field_StringArray = { "foo", "bar" }; + [Export(PropertyHint.Enum, "A,B,C")] private String[] field_StringArrayEnum = { "foo", "bar" }; + [Export] private Vector2[] field_Vector2Array = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right }; + [Export] private Vector3[] field_Vector3Array = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right }; + [Export] private Color[] field_ColorArray = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige }; + [Export] private Godot.Object[] field_GodotObjectOrDerivedArray = { null }; + [Export] private StringName[] field_StringNameArray = { "foo", "bar" }; + [Export] private NodePath[] field_NodePathArray = { "foo", "bar" }; + [Export] private RID[] field_RIDArray = { default, default, default }; + + // Variant + [Export] private Variant field_Variant = "foo"; + + // Classes + [Export] private Godot.Object field_GodotObjectOrDerived; + [Export] private Godot.Texture field_GodotResourceTexture; + [Export] private StringName field_StringName = new StringName("foo"); + [Export] private NodePath field_NodePath = new NodePath("foo"); + [Export] private RID field_RID; + + [Export] private Godot.Collections.Dictionary field_GodotDictionary = + new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } }; + + [Export] private Godot.Collections.Array field_GodotArray = + new() { "foo", 10, Vector2.Up, Colors.Chocolate }; + + [Export] private Godot.Collections.Dictionary<string, bool> field_GodotGenericDictionary = + new() { { "foo", true }, { "bar", false } }; + + [Export] private Godot.Collections.Array<int> field_GodotGenericArray = + new() { 0, 1, 2, 3, 4, 5, 6 }; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs new file mode 100644 index 0000000000..4a0e8075f0 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable CS0169 +#pragma warning disable CS0414 + +namespace Godot.SourceGenerators.Sample +{ + [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public partial class ExportedProperties : Godot.Object + { + [Export] private Boolean property_Boolean { get; set; } = true; + [Export] private Char property_Char { get; set; } = 'f'; + [Export] private SByte property_SByte { get; set; } = 10; + [Export] private Int16 property_Int16 { get; set; } = 10; + [Export] private Int32 property_Int32 { get; set; } = 10; + [Export] private Int64 property_Int64 { get; set; } = 10; + [Export] private Byte property_Byte { get; set; } = 10; + [Export] private UInt16 property_UInt16 { get; set; } = 10; + [Export] private UInt32 property_UInt32 { get; set; } = 10; + [Export] private UInt64 property_UInt64 { get; set; } = 10; + [Export] private Single property_Single { get; set; } = 10; + [Export] private Double property_Double { get; set; } = 10; + [Export] private String property_String { get; set; } = "foo"; + + // Godot structs + [Export] private Vector2 property_Vector2 { get; set; } = new(10f, 10f); + [Export] private Vector2i property_Vector2i { get; set; } = Vector2i.Up; + [Export] private Rect2 property_Rect2 { get; set; } = new(new Vector2(10f, 10f), new Vector2(10f, 10f)); + [Export] private Rect2i property_Rect2i { get; set; } = new(new Vector2i(10, 10), new Vector2i(10, 10)); + [Export] private Transform2D property_Transform2D { get; set; } = Transform2D.Identity; + [Export] private Vector3 property_Vector3 { get; set; } = new(10f, 10f, 10f); + [Export] private Vector3i property_Vector3i { get; set; } = Vector3i.Back; + [Export] private Basis property_Basis { get; set; } = new Basis(Quaternion.Identity); + [Export] private Quaternion property_Quaternion { get; set; } = new Quaternion(Basis.Identity); + [Export] private Transform3D property_Transform3D { get; set; } = Transform3D.Identity; + [Export] private Vector4 property_Vector4 { get; set; } = new(10f, 10f, 10f, 10f); + [Export] private Vector4i property_Vector4i { get; set; } = Vector4i.One; + [Export] private Projection property_Projection { get; set; } = Projection.Identity; + [Export] private AABB property_AABB { get; set; } = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f)); + [Export] private Color property_Color { get; set; } = Colors.Aquamarine; + [Export] private Plane property_Plane { get; set; } = Plane.PlaneXZ; + [Export] private Callable property_Callable { get; set; } = new Callable(Engine.GetMainLoop(), "_process"); + [Export] private SignalInfo property_SignalInfo { get; set; } = new SignalInfo(Engine.GetMainLoop(), "property_list_changed"); + + // Enums + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyEnum + { + A, + B, + C + } + + [Export] private MyEnum property_Enum { get; set; } = MyEnum.C; + + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyFlagsEnum + { + A, + B, + C + } + + [Export] private MyFlagsEnum property_FlagsEnum { get; set; } = MyFlagsEnum.C; + + // Arrays + [Export] private Byte[] property_ByteArray { get; set; } = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int32[] property_Int32Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int64[] property_Int64Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Single[] property_SingleArray { get; set; } = { 0f, 1f, 2f, 3f, 4f, 5f, 6f }; + [Export] private Double[] property_DoubleArray { get; set; } = { 0d, 1d, 2d, 3d, 4d, 5d, 6d }; + [Export] private String[] property_StringArray { get; set; } = { "foo", "bar" }; + [Export(PropertyHint.Enum, "A,B,C")] private String[] property_StringArrayEnum { get; set; } = { "foo", "bar" }; + [Export] private Vector2[] property_Vector2Array { get; set; } = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right }; + [Export] private Vector3[] property_Vector3Array { get; set; } = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right }; + [Export] private Color[] property_ColorArray { get; set; } = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige }; + [Export] private Godot.Object[] property_GodotObjectOrDerivedArray { get; set; } = { null }; + [Export] private StringName[] field_StringNameArray { get; set; } = { "foo", "bar" }; + [Export] private NodePath[] field_NodePathArray { get; set; } = { "foo", "bar" }; + [Export] private RID[] field_RIDArray { get; set; } = { default, default, default }; + + // Variant + [Export] private Variant property_Variant { get; set; } = "foo"; + + // Classes + [Export] private Godot.Object property_GodotObjectOrDerived { get; set; } + [Export] private Godot.Texture property_GodotResourceTexture { get; set; } + [Export] private StringName property_StringName { get; set; } = new StringName("foo"); + [Export] private NodePath property_NodePath { get; set; } = new NodePath("foo"); + [Export] private RID property_RID { get; set; } + + [Export] private Godot.Collections.Dictionary property_GodotDictionary { get; set; } = + new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } }; + + [Export] private Godot.Collections.Array property_GodotArray { get; set; } = + new() { "foo", 10, Vector2.Up, Colors.Chocolate }; + + [Export] private Godot.Collections.Dictionary<string, bool> property_GodotGenericDictionary { get; set; } = + new() { { "foo", true }, { "bar", false } }; + + [Export] private Godot.Collections.Array<int> property_GodotGenericArray { get; set; } = + new() { 0, 1, 2, 3, 4, 5, 6 }; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs index 2ddb8880c2..b21b035b4d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs @@ -1,16 +1,21 @@ +#pragma warning disable CS0169 + namespace Godot.SourceGenerators.Sample { partial class Generic<T> : Godot.Object { + private int _field; } // Generic again but different generic parameters partial class Generic<T, R> : Godot.Object { + private int _field; } // Generic again but without generic parameters partial class Generic : Godot.Object { + private int _field; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj index 24f7909861..8e78e0385d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -1,12 +1,14 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> + <TargetFramework>net6.0</TargetFramework> </PropertyGroup> <PropertyGroup> <!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk --> <GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir> + <!-- For compiling GetGodotPropertyDefaultValues. --> + <DefineConstants>$(DefineConstants);TOOLS</DefineConstants> </PropertyGroup> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs new file mode 100644 index 0000000000..618ba24abc --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Godot.SourceGenerators.Sample; + +[SuppressMessage("ReSharper", "RedundantNameQualifier")] +public partial class Methods : Godot.Object +{ + private void MethodWithOverload() + { + } + + private void MethodWithOverload(int a) + { + } + + private void MethodWithOverload(int a, int b) + { + } + + // Should be ignored. The previous one is picked. + // ReSharper disable once UnusedMember.Local + private void MethodWithOverload(float a, float b) + { + } + + // Generic methods should be ignored. + // ReSharper disable once UnusedMember.Local + private void GenericMethod<T>(T t) + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs new file mode 100644 index 0000000000..a1667dbb8f --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs @@ -0,0 +1,36 @@ +#pragma warning disable CS0169 + +namespace Godot.SourceGenerators.Sample +{ + public partial class ScriptBoilerplate : Node + { + private NodePath _nodePath; + private int _velocity; + + public override void _Process(float delta) + { + _ = delta; + + base._Process(delta); + } + + public int Bazz(StringName name) + { + _ = name; + return 1; + } + + public void IgnoreThisMethodWithByRefParams(ref int a) + { + _ = a; + } + } + + partial struct OuterClass + { + public partial class NesterClass : RefCounted + { + public override Variant _Get(StringName property) => default; + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 4867c986e6..3dfa8000ba 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -1,5 +1,7 @@ +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; namespace Godot.SourceGenerators { @@ -14,12 +16,11 @@ namespace Godot.SourceGenerators "Missing partial modifier on declaration of type '" + $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'"; - string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " + - "declared with the partial modifier or annotated with the " + - $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'."; + string description = $"{message}. Subclasses of '{GodotClasses.Object}' " + + "must be declared with the partial modifier."; context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GODOT-G0001", + new DiagnosticDescriptor(id: "GD0001", title: message, messageFormat: message, category: "Usage", @@ -29,5 +30,307 @@ namespace Godot.SourceGenerators cds.GetLocation(), cds.SyntaxTree.FilePath)); } + + public static void ReportNonPartialGodotScriptOuterClass( + GeneratorExecutionContext context, + TypeDeclarationSyntax outerTypeDeclSyntax + ) + { + var outerSymbol = context.Compilation + .GetSemanticModel(outerTypeDeclSyntax.SyntaxTree) + .GetDeclaredSymbol(outerTypeDeclSyntax); + + string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? + namedTypeSymbol.FullQualifiedName() : + "type not found"; + + string message = + $"Missing partial modifier on declaration of type '{fullQualifiedName}', " + + $"which contains one or more subclasses of '{GodotClasses.Object}'"; + + string description = $"{message}. Subclasses of '{GodotClasses.Object}' and their " + + "containing types must be declared with the partial modifier."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0002", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + outerTypeDeclSyntax.GetLocation(), + outerTypeDeclSyntax.SyntaxTree.FilePath)); + } + + public static void ReportExportedMemberIsStatic( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + bool isField = exportedMemberSymbol is IFieldSymbol; + + string message = $"Attempted to export static {(isField ? "field" : "property")}: " + + $"'{exportedMemberSymbol.ToDisplayString()}'"; + + string description = $"{message}. Only instance fields and properties can be exported." + + " Remove the 'static' modifier or the '[Export]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0101", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportExportedMemberTypeNotSupported( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + bool isField = exportedMemberSymbol is IFieldSymbol; + + string message = $"The type of the exported {(isField ? "field" : "property")} " + + $"is not supported: '{exportedMemberSymbol.ToDisplayString()}'"; + + string description = $"{message}. Use a supported type or remove the '[Export]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0102", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportExportedMemberIsReadOnly( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + bool isField = exportedMemberSymbol is IFieldSymbol; + + string message = $"The exported {(isField ? "field" : "property")} " + + $"is read-only: '{exportedMemberSymbol.ToDisplayString()}'"; + + string description = isField ? + $"{message}. Exported fields cannot be read-only." : + $"{message}. Exported properties must be writable."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0103", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportExportedMemberIsWriteOnly( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + + string message = $"The exported property is write-only: '{exportedMemberSymbol.ToDisplayString()}'"; + + string description = $"{message}. Exported properties must be readable."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0104", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportSignalDelegateMissingSuffix( + GeneratorExecutionContext context, + INamedTypeSymbol delegateSymbol) + { + var locations = delegateSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + + string message = "The name of the delegate must end with 'EventHandler': " + + delegateSymbol.ToDisplayString() + + $". Did you mean '{delegateSymbol.Name}EventHandler'?"; + + string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0201", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportSignalParameterTypeNotSupported( + GeneratorExecutionContext context, + IParameterSymbol parameterSymbol) + { + var locations = parameterSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + + string message = "The parameter of the delegate signature of the signal " + + $"is not supported: '{parameterSymbol.ToDisplayString()}'"; + + string description = $"{message}. Use supported types only or remove the '[Signal]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0202", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportSignalDelegateSignatureMustReturnVoid( + GeneratorExecutionContext context, + INamedTypeSymbol delegateSymbol) + { + var locations = delegateSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + + string message = "The delegate signature of the signal " + + $"must return void: '{delegateSymbol.ToDisplayString()}'"; + + string description = $"{message}. Return void or remove the '[Signal]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0203", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static readonly DiagnosticDescriptor GenericTypeArgumentMustBeVariantRule = + new DiagnosticDescriptor(id: "GD0301", + title: "The generic type argument must be a Variant compatible type", + messageFormat: "The generic type argument must be a Variant compatible type: {0}", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument."); + + public static void ReportGenericTypeArgumentMustBeVariant( + SyntaxNodeAnalysisContext context, + SyntaxNode typeArgumentSyntax, + ISymbol typeArgumentSymbol) + { + string message = "The generic type argument " + + $"must be a Variant compatible type: '{typeArgumentSymbol.ToDisplayString()}'"; + + string description = $"{message}. Use a Variant compatible type as the generic type argument."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0301", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + typeArgumentSyntax.GetLocation(), + typeArgumentSyntax.SyntaxTree.FilePath)); + } + + public static readonly DiagnosticDescriptor GenericTypeParameterMustBeVariantAnnotatedRule = + new DiagnosticDescriptor(id: "GD0302", + title: "The generic type parameter must be annotated with the MustBeVariant attribute", + messageFormat: "The generic type argument must be a Variant type: {0}", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The generic type argument must be a Variant type. Use a Variant type as the generic type argument."); + + public static void ReportGenericTypeParameterMustBeVariantAnnotated( + SyntaxNodeAnalysisContext context, + SyntaxNode typeArgumentSyntax, + ISymbol typeArgumentSymbol) + { + string message = "The generic type parameter must be annotated with the MustBeVariant attribute"; + + string description = $"{message}. Add the MustBeVariant attribute to the generic type parameter."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0302", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + typeArgumentSyntax.GetLocation(), + typeArgumentSyntax.SyntaxTree.FilePath)); + } + + public static readonly DiagnosticDescriptor TypeArgumentParentSymbolUnhandledRule = + new DiagnosticDescriptor(id: "GD0303", + title: "The generic type parameter must be annotated with the MustBeVariant attribute", + messageFormat: "The generic type argument must be a Variant type: {0}", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The generic type argument must be a Variant type. Use a Variant type as the generic type argument."); + + public static void ReportTypeArgumentParentSymbolUnhandled( + SyntaxNodeAnalysisContext context, + SyntaxNode typeArgumentSyntax, + ISymbol parentSymbol) + { + string message = $"Symbol '{parentSymbol.ToDisplayString()}' parent of a type argument " + + "that must be Variant compatible was not handled."; + + string description = $"{message}. Handle type arguments that are children of the unhandled symbol type."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0303", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + typeArgumentSyntax.GetLocation(), + typeArgumentSyntax.SyntaxTree.FilePath)); + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index e16f72f43a..d868678179 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -13,30 +15,65 @@ namespace Godot.SourceGenerators ) => context.AnalyzerConfigOptions.GlobalOptions .TryGetValue("build_property." + property, out value); - private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName) - { - if (symbol == null) - return false; + public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context) + => context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) && + toggle != null && + toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase); + + public static bool IsGodotToolsProject(this GeneratorExecutionContext context) + => context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) && + toggle != null && + toggle.Equals("true", StringComparison.OrdinalIgnoreCase); - while (true) + public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName) + { + while (symbol != null) { - if (symbol.ToString() == baseName) + if (symbol.ContainingAssembly.Name == assemblyName && + symbol.ToString() == typeFullName) { return true; } - if (symbol.BaseType != null) - { - symbol = symbol.BaseType; - continue; - } - - break; + symbol = symbol.BaseType; } return false; } + public static INamedTypeSymbol? GetGodotScriptNativeClass(this INamedTypeSymbol classTypeSymbol) + { + var symbol = classTypeSymbol; + + while (symbol != null) + { + if (symbol.ContainingAssembly.Name == "GodotSharp") + return symbol; + + symbol = symbol.BaseType; + } + + return null; + } + + public static string? GetGodotScriptNativeClassName(this INamedTypeSymbol classTypeSymbol) + { + var nativeType = classTypeSymbol.GetGodotScriptNativeClass(); + + if (nativeType == null) + return null; + + var godotClassNameAttr = nativeType.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGodotClassNameAttribute() ?? false); + + string? godotClassName = null; + + if (godotClassNameAttr is { ConstructorArguments: { Length: > 0 } }) + godotClassName = godotClassNameAttr.ConstructorArguments[0].Value?.ToString(); + + return godotClassName ?? nativeType.Name; + } + private static bool IsGodotScriptClass( this ClassDeclarationSyntax cds, Compilation compilation, out INamedTypeSymbol? symbol @@ -47,7 +84,7 @@ namespace Godot.SourceGenerators var classTypeSymbol = sm.GetDeclaredSymbol(cds); if (classTypeSymbol?.BaseType == null - || !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object)) + || !classTypeSymbol.BaseType.InheritsFrom("GodotSharp", GodotClasses.Object)) { symbol = null; return false; @@ -69,21 +106,174 @@ namespace Godot.SourceGenerators } } - public static bool IsPartial(this ClassDeclarationSyntax cds) + public static bool IsNested(this TypeDeclarationSyntax cds) + => cds.Parent is TypeDeclarationSyntax; + + public static bool IsPartial(this TypeDeclarationSyntax cds) => cds.Modifiers.Any(SyntaxKind.PartialKeyword); - public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol) - => symbol.GetAttributes().Any(attr => - attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr); + public static bool AreAllOuterTypesPartial( + this TypeDeclarationSyntax cds, + out TypeDeclarationSyntax? typeMissingPartial + ) + { + SyntaxNode? outerSyntaxNode = cds.Parent; + + while (outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax) + { + if (!outerTypeDeclSyntax.IsPartial()) + { + typeMissingPartial = outerTypeDeclSyntax; + return false; + } + + outerSyntaxNode = outerSyntaxNode.Parent; + } + + typeMissingPartial = null; + return true; + } + + public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol) + { + string? keyword = namedTypeSymbol.DeclaringSyntaxReferences + .OfType<TypeDeclarationSyntax>().FirstOrDefault()? + .Keyword.Text; + + return keyword ?? namedTypeSymbol.TypeKind switch + { + TypeKind.Interface => "interface", + TypeKind.Struct => "struct", + _ => "class" + }; + } private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = SymbolDisplayFormat.FullyQualifiedFormat .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - public static string FullQualifiedName(this INamedTypeSymbol symbol) + public static string FullQualifiedName(this ITypeSymbol symbol) => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + public static string NameWithTypeParameters(this INamedTypeSymbol symbol) + { + return symbol.IsGenericType ? + string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") : + symbol.Name; + } + public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol) => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) + => qualifiedName + // AddSource() doesn't support angle brackets + .Replace("<", "(Of ") + .Replace(">", ")"); + + public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.ExportAttr; + + public static bool IsGodotSignalAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.SignalAttr; + + public static bool IsGodotMustBeVariantAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.MustBeVariantAttr; + + public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.GodotClassNameAttr; + + public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.SystemFlagsAttr; + + public static GodotMethodData? HasGodotCompatibleSignature( + this IMethodSymbol method, + MarshalUtils.TypeCache typeCache + ) + { + if (method.IsGenericMethod) + return null; + + var retSymbol = method.ReturnType; + var retType = method.ReturnsVoid ? + null : + MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache); + + if (retType == null && !method.ReturnsVoid) + return null; + + var parameters = method.Parameters; + + var paramTypes = parameters + // Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may) + .Where(p => p.RefKind == RefKind.None) + // Attempt to determine the variant type + .Select(p => MarshalUtils.ConvertManagedTypeToMarshalType(p.Type, typeCache)) + // Discard parameter types that couldn't be determined (null entries) + .Where(t => t != null).Cast<MarshalType>().ToImmutableArray(); + + // If any parameter type was incompatible, it was discarded so the length won't match + if (parameters.Length > paramTypes.Length) + return null; // Ignore incompatible method + + return new GodotMethodData(method, paramTypes, parameters + .Select(p => p.Type).ToImmutableArray(), retType, retSymbol); + } + + public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature( + this IEnumerable<IMethodSymbol> methods, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var method in methods) + { + var methodData = HasGodotCompatibleSignature(method, typeCache); + + if (methodData != null) + yield return methodData.Value; + } + } + + public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType( + this IEnumerable<IPropertySymbol> properties, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var property in properties) + { + // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (property.IsWriteOnly || property.IsReadOnly) + continue; + + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache); + + if (marshalType == null) + continue; + + yield return new GodotPropertyData(property, marshalType.Value); + } + } + + public static IEnumerable<GodotFieldData> WhereIsGodotCompatibleType( + this IEnumerable<IFieldSymbol> fields, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var field in fields) + { + // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (field.IsReadOnly) + continue; + + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache); + + if (marshalType == null) + continue; + + yield return new GodotFieldData(field, marshalType.Value); + } + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj index 11d8e0f72b..f51b5970c3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> - <LangVersion>8.0</LangVersion> + <LangVersion>9.0</LangVersion> <Nullable>enable</Nullable> </PropertyGroup> <PropertyGroup> @@ -21,21 +21,13 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" /> - <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" /> </ItemGroup> <ItemGroup> <!-- Package the generator in the analyzer directory of the nuget package --> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> <!-- Package the props file --> - <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" /> + <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="true" /> </ItemGroup> - - <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> - <PropertyGroup> - <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> - <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> - </PropertyGroup> - <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> - </Target> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props index f9b47ad5b1..7881ed0a8c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -2,6 +2,7 @@ <ItemGroup> <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk --> <CompilerVisibleProperty Include="GodotProjectDir" /> - <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" /> + <CompilerVisibleProperty Include="GodotSourceGenerators" /> + <CompilerVisibleProperty Include="IsGodotToolsProject" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs index 29e41d155a..1d8ddbabf2 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -3,7 +3,14 @@ namespace Godot.SourceGenerators public static class GodotClasses { public const string Object = "Godot.Object"; - public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute"; public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute"; + public const string ExportAttr = "Godot.ExportAttribute"; + public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute"; + public const string ExportGroupAttr = "Godot.ExportGroupAttribute"; + public const string ExportSubgroupAttr = "Godot.ExportSubgroupAttribute"; + public const string SignalAttr = "Godot.SignalAttribute"; + public const string MustBeVariantAttr = "Godot.MustBeVariantAttribute"; + public const string GodotClassNameAttr = "Godot.GodotClassName"; + public const string SystemFlagsAttr = "System.FlagsAttribute"; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs new file mode 100644 index 0000000000..1a25d684a0 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -0,0 +1,148 @@ +using System; + +namespace Godot.SourceGenerators +{ + // TODO: May need to think about compatibility here. Could Godot change these values between minor versions? + + internal enum VariantType + { + Nil = 0, + Bool = 1, + Int = 2, + Float = 3, + String = 4, + Vector2 = 5, + Vector2i = 6, + Rect2 = 7, + Rect2i = 8, + Vector3 = 9, + Vector3i = 10, + Transform2d = 11, + Vector4 = 12, + Vector4i = 13, + Plane = 14, + Quaternion = 15, + Aabb = 16, + Basis = 17, + Transform3d = 18, + Projection = 19, + Color = 20, + StringName = 21, + NodePath = 22, + Rid = 23, + Object = 24, + Callable = 25, + Signal = 26, + Dictionary = 27, + Array = 28, + PackedByteArray = 29, + PackedInt32Array = 30, + PackedInt64Array = 31, + PackedFloat32Array = 32, + PackedFloat64Array = 33, + PackedStringArray = 34, + PackedVector2Array = 35, + PackedVector3Array = 36, + PackedColorArray = 37, + Max = 38 + } + + internal enum PropertyHint + { + None = 0, + Range = 1, + Enum = 2, + EnumSuggestion = 3, + ExpEasing = 4, + Link = 5, + Flags = 6, + Layers2dRender = 7, + Layers2dPhysics = 8, + Layers2dNavigation = 9, + Layers3dRender = 10, + Layers3dPhysics = 11, + Layers3dNavigation = 12, + File = 13, + Dir = 14, + GlobalFile = 15, + GlobalDir = 16, + ResourceType = 17, + MultilineText = 18, + Expression = 19, + PlaceholderText = 20, + ColorNoAlpha = 21, + ImageCompressLossy = 22, + ImageCompressLossless = 23, + ObjectId = 24, + TypeString = 25, + NodePathToEditedNode = 26, + MethodOfVariantType = 27, + MethodOfBaseType = 28, + MethodOfInstance = 29, + MethodOfScript = 30, + PropertyOfVariantType = 31, + PropertyOfBaseType = 32, + PropertyOfInstance = 33, + PropertyOfScript = 34, + ObjectTooBig = 35, + NodePathValidTypes = 36, + SaveFile = 37, + GlobalSaveFile = 38, + IntIsObjectid = 39, + IntIsPointer = 41, + ArrayType = 40, + LocaleId = 42, + LocalizableString = 43, + NodeType = 44, + Max = 45 + } + + [Flags] + internal enum PropertyUsageFlags + { + None = 0, + Storage = 2, + Editor = 4, + Checkable = 8, + Checked = 16, + Internationalized = 32, + Group = 64, + Category = 128, + Subgroup = 256, + ClassIsBitfield = 512, + NoInstanceState = 1024, + RestartIfChanged = 2048, + ScriptVariable = 4096, + StoreIfNull = 8192, + AnimateAsTrigger = 16384, + UpdateAllIfModified = 32768, + ScriptDefaultValue = 65536, + ClassIsEnum = 131072, + NilIsVariant = 262144, + Internal = 524288, + DoNotShareOnDuplicate = 1048576, + HighEndGfx = 2097152, + NodePathFromSceneRoot = 4194304, + ResourceNotPersistent = 8388608, + KeyingIncrements = 16777216, + DeferredSetResource = 33554432, + EditorInstantiateObject = 67108864, + EditorBasicSetting = 134217728, + Array = 536870912, + Default = 6, + DefaultIntl = 38, + NoEditor = 2 + } + + public enum MethodFlags + { + Normal = 1, + Editor = 2, + Const = 4, + Virtual = 8, + Vararg = 16, + Static = 32, + ObjectCore = 64, + Default = 1 + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs new file mode 100644 index 0000000000..a3ad8cbabd --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs @@ -0,0 +1,62 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + public struct GodotMethodData + { + public GodotMethodData(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes, + ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType, ITypeSymbol? retSymbol) + { + Method = method; + ParamTypes = paramTypes; + ParamTypeSymbols = paramTypeSymbols; + RetType = retType; + RetSymbol = retSymbol; + } + + public IMethodSymbol Method { get; } + public ImmutableArray<MarshalType> ParamTypes { get; } + public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; } + public MarshalType? RetType { get; } + public ITypeSymbol? RetSymbol { get; } + } + + public struct GodotSignalDelegateData + { + public GodotSignalDelegateData(string name, INamedTypeSymbol delegateSymbol, GodotMethodData invokeMethodData) + { + Name = name; + DelegateSymbol = delegateSymbol; + InvokeMethodData = invokeMethodData; + } + + public string Name { get; } + public INamedTypeSymbol DelegateSymbol { get; } + public GodotMethodData InvokeMethodData { get; } + } + + public struct GodotPropertyData + { + public GodotPropertyData(IPropertySymbol propertySymbol, MarshalType type) + { + PropertySymbol = propertySymbol; + Type = type; + } + + public IPropertySymbol PropertySymbol { get; } + public MarshalType Type { get; } + } + + public struct GodotFieldData + { + public GodotFieldData(IFieldSymbol fieldSymbol, MarshalType type) + { + FieldSymbol = fieldSymbol; + Type = type; + } + + public IFieldSymbol FieldSymbol { get; } + public MarshalType Type { get; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs new file mode 100644 index 0000000000..54da6218f3 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs @@ -0,0 +1,60 @@ +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class GodotPluginsInitializerGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.IsGodotToolsProject()) + return; + + string source = + @"using System; +using System.Runtime.InteropServices; +using Godot.Bridge; +using Godot.NativeInterop; + +namespace GodotPlugins.Game +{ + internal static partial class Main + { + [UnmanagedCallersOnly(EntryPoint = ""godotsharp_game_main_init"")] + private static godot_bool InitializeFromGameProject(IntPtr godotDllHandle, IntPtr outManagedCallbacks) + { + try + { + DllImportResolver dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport; + + var coreApiAssembly = typeof(Godot.Object).Assembly; + + NativeLibrary.SetDllImportResolver(coreApiAssembly, dllImportResolver); + + ManagedCallbacks.Create(outManagedCallbacks); + + ScriptManagerBridge.LookupScriptsInAssembly(typeof(GodotPlugins.Game.Main).Assembly); + + return godot_bool.True; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return false.ToGodotBool(); + } + } + } +} +"; + + context.AddSource("GodotPlugins.Game_Generated", + SourceText.From(source, Encoding.UTF8)); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs new file mode 100644 index 0000000000..15f5803bf0 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs @@ -0,0 +1,73 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum MarshalType + { + Boolean, + Char, + SByte, + Int16, + Int32, + Int64, + Byte, + UInt16, + UInt32, + UInt64, + Single, + Double, + String, + + // Godot structs + Vector2, + Vector2i, + Rect2, + Rect2i, + Transform2D, + Vector3, + Vector3i, + Basis, + Quaternion, + Transform3D, + Vector4, + Vector4i, + Projection, + AABB, + Color, + Plane, + Callable, + SignalInfo, + + // Enums + Enum, + + // Arrays + ByteArray, + Int32Array, + Int64Array, + Float32Array, + Float64Array, + StringArray, + Vector2Array, + Vector3Array, + ColorArray, + GodotObjectOrDerivedArray, + SystemArrayOfStringName, + SystemArrayOfNodePath, + SystemArrayOfRID, + + // Variant + Variant, + + // Classes + GodotObjectOrDerived, + StringName, + NodePath, + RID, + GodotDictionary, + GodotArray, + GodotGenericDictionary, + GodotGenericArray, + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs new file mode 100644 index 0000000000..831ac3bdeb --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -0,0 +1,678 @@ +using System; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + internal static class MarshalUtils + { + public class TypeCache + { + public INamedTypeSymbol GodotObjectType { get; } + + public TypeCache(Compilation compilation) + { + INamedTypeSymbol GetTypeByMetadataNameOrThrow(string fullyQualifiedMetadataName) + { + return compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? + throw new InvalidOperationException("Type not found: " + fullyQualifiedMetadataName); + } + + GodotObjectType = GetTypeByMetadataNameOrThrow("Godot.Object"); + } + } + + public static VariantType? ConvertMarshalTypeToVariantType(MarshalType marshalType) + => marshalType switch + { + MarshalType.Boolean => VariantType.Bool, + MarshalType.Char => VariantType.Int, + MarshalType.SByte => VariantType.Int, + MarshalType.Int16 => VariantType.Int, + MarshalType.Int32 => VariantType.Int, + MarshalType.Int64 => VariantType.Int, + MarshalType.Byte => VariantType.Int, + MarshalType.UInt16 => VariantType.Int, + MarshalType.UInt32 => VariantType.Int, + MarshalType.UInt64 => VariantType.Int, + MarshalType.Single => VariantType.Float, + MarshalType.Double => VariantType.Float, + MarshalType.String => VariantType.String, + MarshalType.Vector2 => VariantType.Vector2, + MarshalType.Vector2i => VariantType.Vector2i, + MarshalType.Rect2 => VariantType.Rect2, + MarshalType.Rect2i => VariantType.Rect2i, + MarshalType.Transform2D => VariantType.Transform2d, + MarshalType.Vector3 => VariantType.Vector3, + MarshalType.Vector3i => VariantType.Vector3i, + MarshalType.Basis => VariantType.Basis, + MarshalType.Quaternion => VariantType.Quaternion, + MarshalType.Transform3D => VariantType.Transform3d, + MarshalType.Vector4 => VariantType.Vector4, + MarshalType.Vector4i => VariantType.Vector4i, + MarshalType.Projection => VariantType.Projection, + MarshalType.AABB => VariantType.Aabb, + MarshalType.Color => VariantType.Color, + MarshalType.Plane => VariantType.Plane, + MarshalType.Callable => VariantType.Callable, + MarshalType.SignalInfo => VariantType.Signal, + MarshalType.Enum => VariantType.Int, + MarshalType.ByteArray => VariantType.PackedByteArray, + MarshalType.Int32Array => VariantType.PackedInt32Array, + MarshalType.Int64Array => VariantType.PackedInt64Array, + MarshalType.Float32Array => VariantType.PackedFloat32Array, + MarshalType.Float64Array => VariantType.PackedFloat64Array, + MarshalType.StringArray => VariantType.PackedStringArray, + MarshalType.Vector2Array => VariantType.PackedVector2Array, + MarshalType.Vector3Array => VariantType.PackedVector3Array, + MarshalType.ColorArray => VariantType.PackedColorArray, + MarshalType.GodotObjectOrDerivedArray => VariantType.Array, + MarshalType.SystemArrayOfStringName => VariantType.Array, + MarshalType.SystemArrayOfNodePath => VariantType.Array, + MarshalType.SystemArrayOfRID => VariantType.Array, + MarshalType.Variant => VariantType.Nil, + MarshalType.GodotObjectOrDerived => VariantType.Object, + MarshalType.StringName => VariantType.StringName, + MarshalType.NodePath => VariantType.NodePath, + MarshalType.RID => VariantType.Rid, + MarshalType.GodotDictionary => VariantType.Dictionary, + MarshalType.GodotArray => VariantType.Array, + MarshalType.GodotGenericDictionary => VariantType.Dictionary, + MarshalType.GodotGenericArray => VariantType.Array, + _ => null + }; + + public static MarshalType? ConvertManagedTypeToMarshalType(ITypeSymbol type, TypeCache typeCache) + { + var specialType = type.SpecialType; + + switch (specialType) + { + case SpecialType.System_Boolean: + return MarshalType.Boolean; + case SpecialType.System_Char: + return MarshalType.Char; + case SpecialType.System_SByte: + return MarshalType.SByte; + case SpecialType.System_Int16: + return MarshalType.Int16; + case SpecialType.System_Int32: + return MarshalType.Int32; + case SpecialType.System_Int64: + return MarshalType.Int64; + case SpecialType.System_Byte: + return MarshalType.Byte; + case SpecialType.System_UInt16: + return MarshalType.UInt16; + case SpecialType.System_UInt32: + return MarshalType.UInt32; + case SpecialType.System_UInt64: + return MarshalType.UInt64; + case SpecialType.System_Single: + return MarshalType.Single; + case SpecialType.System_Double: + return MarshalType.Double; + case SpecialType.System_String: + return MarshalType.String; + default: + { + var typeKind = type.TypeKind; + + if (typeKind == TypeKind.Enum) + return MarshalType.Enum; + + if (typeKind == TypeKind.Struct) + { + if (type.ContainingAssembly.Name == "GodotSharp" && + type.ContainingNamespace.Name == "Godot") + { + return type switch + { + { Name: "Vector2" } => MarshalType.Vector2, + { Name: "Vector2i" } => MarshalType.Vector2i, + { Name: "Rect2" } => MarshalType.Rect2, + { Name: "Rect2i" } => MarshalType.Rect2i, + { Name: "Transform2D" } => MarshalType.Transform2D, + { Name: "Vector3" } => MarshalType.Vector3, + { Name: "Vector3i" } => MarshalType.Vector3i, + { Name: "Basis" } => MarshalType.Basis, + { Name: "Quaternion" } => MarshalType.Quaternion, + { Name: "Transform3D" } => MarshalType.Transform3D, + { Name: "Vector4" } => MarshalType.Vector4, + { Name: "Vector4i" } => MarshalType.Vector4i, + { Name: "Projection" } => MarshalType.Projection, + { Name: "AABB" } => MarshalType.AABB, + { Name: "Color" } => MarshalType.Color, + { Name: "Plane" } => MarshalType.Plane, + { Name: "RID" } => MarshalType.RID, + { Name: "Callable" } => MarshalType.Callable, + { Name: "SignalInfo" } => MarshalType.SignalInfo, + { Name: "Variant" } => MarshalType.Variant, + _ => null + }; + } + } + else if (typeKind == TypeKind.Array) + { + var arrayType = (IArrayTypeSymbol)type; + var elementType = arrayType.ElementType; + + switch (elementType.SpecialType) + { + case SpecialType.System_Byte: + return MarshalType.ByteArray; + case SpecialType.System_Int32: + return MarshalType.Int32Array; + case SpecialType.System_Int64: + return MarshalType.Int64Array; + case SpecialType.System_Single: + return MarshalType.Float32Array; + case SpecialType.System_Double: + return MarshalType.Float64Array; + case SpecialType.System_String: + return MarshalType.StringArray; + } + + if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType)) + return MarshalType.GodotObjectOrDerivedArray; + + if (elementType.ContainingAssembly.Name == "GodotSharp" && + elementType.ContainingNamespace.Name == "Godot") + { + switch (elementType) + { + case { Name: "Vector2" }: + return MarshalType.Vector2Array; + case { Name: "Vector3" }: + return MarshalType.Vector3Array; + case { Name: "Color" }: + return MarshalType.ColorArray; + case { Name: "StringName" }: + return MarshalType.SystemArrayOfStringName; + case { Name: "NodePath" }: + return MarshalType.SystemArrayOfNodePath; + case { Name: "RID" }: + return MarshalType.SystemArrayOfRID; + } + } + + return null; + } + else + { + if (type.SimpleDerivesFrom(typeCache.GodotObjectType)) + return MarshalType.GodotObjectOrDerived; + + if (type.ContainingAssembly.Name == "GodotSharp") + { + switch (type.ContainingNamespace.Name) + { + case "Godot": + return type switch + { + { Name: "StringName" } => MarshalType.StringName, + { Name: "NodePath" } => MarshalType.NodePath, + _ => null + }; + case "Collections" + when type.ContainingNamespace.FullQualifiedName() == "Godot.Collections": + return type switch + { + { Name: "Dictionary" } => + type is INamedTypeSymbol { IsGenericType: false } ? + MarshalType.GodotDictionary : + MarshalType.GodotGenericDictionary, + { Name: "Array" } => + type is INamedTypeSymbol { IsGenericType: false } ? + MarshalType.GodotArray : + MarshalType.GodotGenericArray, + _ => null + }; + } + } + } + + break; + } + } + + return null; + } + + private static bool SimpleDerivesFrom(this ITypeSymbol? type, ITypeSymbol candidateBaseType) + { + while (type != null) + { + if (SymbolEqualityComparer.Default.Equals(type, candidateBaseType)) + return true; + + type = type.BaseType; + } + + return false; + } + + public static ITypeSymbol? GetArrayElementType(ITypeSymbol typeSymbol) + { + if (typeSymbol.TypeKind == TypeKind.Array) + { + var arrayType = (IArrayTypeSymbol)typeSymbol; + return arrayType.ElementType; + } + + if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType) + return genericType.TypeArguments.FirstOrDefault(); + + return null; + } + + private static StringBuilder Append(this StringBuilder source, string a, string b) + => source.Append(a).Append(b); + + private static StringBuilder Append(this StringBuilder source, string a, string b, string c) + => source.Append(a).Append(b).Append(c); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d) + => source.Append(a).Append(b).Append(c).Append(d); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e) + => source.Append(a).Append(b).Append(c).Append(d).Append(e); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e, string f) + => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e, string f, string g) + => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e, string f, string g, string h) + => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g).Append(h); + + private const string VariantUtils = "global::Godot.NativeInterop.VariantUtils"; + + public static StringBuilder AppendNativeVariantToManagedExpr(this StringBuilder source, + string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType) + { + return marshalType switch + { + MarshalType.Boolean => + source.Append(VariantUtils, ".ConvertToBool(", inputExpr, ")"), + MarshalType.Char => + source.Append("(char)", VariantUtils, ".ConvertToUInt16(", inputExpr, ")"), + MarshalType.SByte => + source.Append(VariantUtils, ".ConvertToInt8(", inputExpr, ")"), + MarshalType.Int16 => + source.Append(VariantUtils, ".ConvertToInt16(", inputExpr, ")"), + MarshalType.Int32 => + source.Append(VariantUtils, ".ConvertToInt32(", inputExpr, ")"), + MarshalType.Int64 => + source.Append(VariantUtils, ".ConvertToInt64(", inputExpr, ")"), + MarshalType.Byte => + source.Append(VariantUtils, ".ConvertToUInt8(", inputExpr, ")"), + MarshalType.UInt16 => + source.Append(VariantUtils, ".ConvertToUInt16(", inputExpr, ")"), + MarshalType.UInt32 => + source.Append(VariantUtils, ".ConvertToUInt32(", inputExpr, ")"), + MarshalType.UInt64 => + source.Append(VariantUtils, ".ConvertToUInt64(", inputExpr, ")"), + MarshalType.Single => + source.Append(VariantUtils, ".ConvertToFloat32(", inputExpr, ")"), + MarshalType.Double => + source.Append(VariantUtils, ".ConvertToFloat64(", inputExpr, ")"), + MarshalType.String => + source.Append(VariantUtils, ".ConvertToStringObject(", inputExpr, ")"), + MarshalType.Vector2 => + source.Append(VariantUtils, ".ConvertToVector2(", inputExpr, ")"), + MarshalType.Vector2i => + source.Append(VariantUtils, ".ConvertToVector2i(", inputExpr, ")"), + MarshalType.Rect2 => + source.Append(VariantUtils, ".ConvertToRect2(", inputExpr, ")"), + MarshalType.Rect2i => + source.Append(VariantUtils, ".ConvertToRect2i(", inputExpr, ")"), + MarshalType.Transform2D => + source.Append(VariantUtils, ".ConvertToTransform2D(", inputExpr, ")"), + MarshalType.Vector3 => + source.Append(VariantUtils, ".ConvertToVector3(", inputExpr, ")"), + MarshalType.Vector3i => + source.Append(VariantUtils, ".ConvertToVector3i(", inputExpr, ")"), + MarshalType.Basis => + source.Append(VariantUtils, ".ConvertToBasis(", inputExpr, ")"), + MarshalType.Quaternion => + source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"), + MarshalType.Transform3D => + source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"), + MarshalType.Vector4 => + source.Append(VariantUtils, ".ConvertToVector4(", inputExpr, ")"), + MarshalType.Vector4i => + source.Append(VariantUtils, ".ConvertToVector4i(", inputExpr, ")"), + MarshalType.Projection => + source.Append(VariantUtils, ".ConvertToProjection(", inputExpr, ")"), + MarshalType.AABB => + source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"), + MarshalType.Color => + source.Append(VariantUtils, ".ConvertToColor(", inputExpr, ")"), + MarshalType.Plane => + source.Append(VariantUtils, ".ConvertToPlane(", inputExpr, ")"), + MarshalType.Callable => + source.Append(VariantUtils, ".ConvertToCallableManaged(", inputExpr, ")"), + MarshalType.SignalInfo => + source.Append(VariantUtils, ".ConvertToSignalInfo(", inputExpr, ")"), + MarshalType.Enum => + source.Append("(", typeSymbol.FullQualifiedName(), + ")", VariantUtils, ".ConvertToInt32(", inputExpr, ")"), + MarshalType.ByteArray => + source.Append(VariantUtils, ".ConvertAsPackedByteArrayToSystemArray(", inputExpr, ")"), + MarshalType.Int32Array => + source.Append(VariantUtils, ".ConvertAsPackedInt32ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Int64Array => + source.Append(VariantUtils, ".ConvertAsPackedInt64ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Float32Array => + source.Append(VariantUtils, ".ConvertAsPackedFloat32ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Float64Array => + source.Append(VariantUtils, ".ConvertAsPackedFloat64ArrayToSystemArray(", inputExpr, ")"), + MarshalType.StringArray => + source.Append(VariantUtils, ".ConvertAsPackedStringArrayToSystemArray(", inputExpr, ")"), + MarshalType.Vector2Array => + source.Append(VariantUtils, ".ConvertAsPackedVector2ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Vector3Array => + source.Append(VariantUtils, ".ConvertAsPackedVector3ArrayToSystemArray(", inputExpr, ")"), + MarshalType.ColorArray => + source.Append(VariantUtils, ".ConvertAsPackedColorArrayToSystemArray(", inputExpr, ")"), + MarshalType.GodotObjectOrDerivedArray => + source.Append(VariantUtils, ".ConvertToSystemArrayOfGodotObject<", + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">(", inputExpr, ")"), + MarshalType.SystemArrayOfStringName => + source.Append(VariantUtils, ".ConvertToSystemArrayOfStringName(", inputExpr, ")"), + MarshalType.SystemArrayOfNodePath => + source.Append(VariantUtils, ".ConvertToSystemArrayOfNodePath(", inputExpr, ")"), + MarshalType.SystemArrayOfRID => + source.Append(VariantUtils, ".ConvertToSystemArrayOfRID(", inputExpr, ")"), + MarshalType.Variant => + source.Append("global::Godot.Variant.CreateCopyingBorrowed(", inputExpr, ")"), + MarshalType.GodotObjectOrDerived => + source.Append("(", typeSymbol.FullQualifiedName(), + ")", VariantUtils, ".ConvertToGodotObject(", inputExpr, ")"), + MarshalType.StringName => + source.Append(VariantUtils, ".ConvertToStringNameObject(", inputExpr, ")"), + MarshalType.NodePath => + source.Append(VariantUtils, ".ConvertToNodePathObject(", inputExpr, ")"), + MarshalType.RID => + source.Append(VariantUtils, ".ConvertToRID(", inputExpr, ")"), + MarshalType.GodotDictionary => + source.Append(VariantUtils, ".ConvertToDictionaryObject(", inputExpr, ")"), + MarshalType.GodotArray => + source.Append(VariantUtils, ".ConvertToArrayObject(", inputExpr, ")"), + MarshalType.GodotGenericDictionary => + source.Append(VariantUtils, ".ConvertToDictionaryObject<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">(", inputExpr, ")"), + MarshalType.GodotGenericArray => + source.Append(VariantUtils, ".ConvertToArrayObject<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">(", inputExpr, ")"), + _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type") + }; + } + + public static StringBuilder AppendManagedToNativeVariantExpr( + this StringBuilder source, string inputExpr, MarshalType marshalType) + { + return marshalType switch + { + MarshalType.Boolean => + source.Append(VariantUtils, ".CreateFromBool(", inputExpr, ")"), + MarshalType.Char => + source.Append(VariantUtils, ".CreateFromInt((ushort)", inputExpr, ")"), + MarshalType.SByte => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Int16 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Int32 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Int64 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Byte => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.UInt16 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.UInt32 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.UInt64 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Single => + source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"), + MarshalType.Double => + source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"), + MarshalType.String => + source.Append(VariantUtils, ".CreateFromString(", inputExpr, ")"), + MarshalType.Vector2 => + source.Append(VariantUtils, ".CreateFromVector2(", inputExpr, ")"), + MarshalType.Vector2i => + source.Append(VariantUtils, ".CreateFromVector2i(", inputExpr, ")"), + MarshalType.Rect2 => + source.Append(VariantUtils, ".CreateFromRect2(", inputExpr, ")"), + MarshalType.Rect2i => + source.Append(VariantUtils, ".CreateFromRect2i(", inputExpr, ")"), + MarshalType.Transform2D => + source.Append(VariantUtils, ".CreateFromTransform2D(", inputExpr, ")"), + MarshalType.Vector3 => + source.Append(VariantUtils, ".CreateFromVector3(", inputExpr, ")"), + MarshalType.Vector3i => + source.Append(VariantUtils, ".CreateFromVector3i(", inputExpr, ")"), + MarshalType.Basis => + source.Append(VariantUtils, ".CreateFromBasis(", inputExpr, ")"), + MarshalType.Quaternion => + source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"), + MarshalType.Transform3D => + source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"), + MarshalType.Vector4 => + source.Append(VariantUtils, ".CreateFromVector4(", inputExpr, ")"), + MarshalType.Vector4i => + source.Append(VariantUtils, ".CreateFromVector4i(", inputExpr, ")"), + MarshalType.Projection => + source.Append(VariantUtils, ".CreateFromProjection(", inputExpr, ")"), + MarshalType.AABB => + source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"), + MarshalType.Color => + source.Append(VariantUtils, ".CreateFromColor(", inputExpr, ")"), + MarshalType.Plane => + source.Append(VariantUtils, ".CreateFromPlane(", inputExpr, ")"), + MarshalType.Callable => + source.Append(VariantUtils, ".CreateFromCallable(", inputExpr, ")"), + MarshalType.SignalInfo => + source.Append(VariantUtils, ".CreateFromSignalInfo(", inputExpr, ")"), + MarshalType.Enum => + source.Append(VariantUtils, ".CreateFromInt((int)", inputExpr, ")"), + MarshalType.ByteArray => + source.Append(VariantUtils, ".CreateFromPackedByteArray(", inputExpr, ")"), + MarshalType.Int32Array => + source.Append(VariantUtils, ".CreateFromPackedInt32Array(", inputExpr, ")"), + MarshalType.Int64Array => + source.Append(VariantUtils, ".CreateFromPackedInt64Array(", inputExpr, ")"), + MarshalType.Float32Array => + source.Append(VariantUtils, ".CreateFromPackedFloat32Array(", inputExpr, ")"), + MarshalType.Float64Array => + source.Append(VariantUtils, ".CreateFromPackedFloat64Array(", inputExpr, ")"), + MarshalType.StringArray => + source.Append(VariantUtils, ".CreateFromPackedStringArray(", inputExpr, ")"), + MarshalType.Vector2Array => + source.Append(VariantUtils, ".CreateFromPackedVector2Array(", inputExpr, ")"), + MarshalType.Vector3Array => + source.Append(VariantUtils, ".CreateFromPackedVector3Array(", inputExpr, ")"), + MarshalType.ColorArray => + source.Append(VariantUtils, ".CreateFromPackedColorArray(", inputExpr, ")"), + MarshalType.GodotObjectOrDerivedArray => + source.Append(VariantUtils, ".CreateFromSystemArrayOfGodotObject(", inputExpr, ")"), + MarshalType.SystemArrayOfStringName => + source.Append(VariantUtils, ".CreateFromSystemArrayOfStringName(", inputExpr, ")"), + MarshalType.SystemArrayOfNodePath => + source.Append(VariantUtils, ".CreateFromSystemArrayOfNodePath(", inputExpr, ")"), + MarshalType.SystemArrayOfRID => + source.Append(VariantUtils, ".CreateFromSystemArrayOfRID(", inputExpr, ")"), + MarshalType.Variant => + source.Append(inputExpr, ".CopyNativeVariant()"), + MarshalType.GodotObjectOrDerived => + source.Append(VariantUtils, ".CreateFromGodotObject(", inputExpr, ")"), + MarshalType.StringName => + source.Append(VariantUtils, ".CreateFromStringName(", inputExpr, ")"), + MarshalType.NodePath => + source.Append(VariantUtils, ".CreateFromNodePath(", inputExpr, ")"), + MarshalType.RID => + source.Append(VariantUtils, ".CreateFromRID(", inputExpr, ")"), + MarshalType.GodotDictionary => + source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), + MarshalType.GodotArray => + source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), + MarshalType.GodotGenericDictionary => + source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), + MarshalType.GodotGenericArray => + source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), + _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type") + }; + } + + public static StringBuilder AppendVariantToManagedExpr(this StringBuilder source, + string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType) + { + return marshalType switch + { + MarshalType.Boolean => source.Append(inputExpr, ".AsBool()"), + MarshalType.Char => source.Append(inputExpr, ".AsChar()"), + MarshalType.SByte => source.Append(inputExpr, ".AsSByte()"), + MarshalType.Int16 => source.Append(inputExpr, ".AsInt16()"), + MarshalType.Int32 => source.Append(inputExpr, ".AsInt32()"), + MarshalType.Int64 => source.Append(inputExpr, ".AsInt64()"), + MarshalType.Byte => source.Append(inputExpr, ".AsByte()"), + MarshalType.UInt16 => source.Append(inputExpr, ".AsUInt16()"), + MarshalType.UInt32 => source.Append(inputExpr, ".AsUInt32()"), + MarshalType.UInt64 => source.Append(inputExpr, ".AsUInt64()"), + MarshalType.Single => source.Append(inputExpr, ".AsSingle()"), + MarshalType.Double => source.Append(inputExpr, ".AsDouble()"), + MarshalType.String => source.Append(inputExpr, ".AsString()"), + MarshalType.Vector2 => source.Append(inputExpr, ".AsVector2()"), + MarshalType.Vector2i => source.Append(inputExpr, ".AsVector2i()"), + MarshalType.Rect2 => source.Append(inputExpr, ".AsRect2()"), + MarshalType.Rect2i => source.Append(inputExpr, ".AsRect2i()"), + MarshalType.Transform2D => source.Append(inputExpr, ".AsTransform2D()"), + MarshalType.Vector3 => source.Append(inputExpr, ".AsVector3()"), + MarshalType.Vector3i => source.Append(inputExpr, ".AsVector3i()"), + MarshalType.Basis => source.Append(inputExpr, ".AsBasis()"), + MarshalType.Quaternion => source.Append(inputExpr, ".AsQuaternion()"), + MarshalType.Transform3D => source.Append(inputExpr, ".AsTransform3D()"), + MarshalType.Vector4 => source.Append(inputExpr, ".AsVector4()"), + MarshalType.Vector4i => source.Append(inputExpr, ".AsVector4i()"), + MarshalType.Projection => source.Append(inputExpr, ".AsProjection()"), + MarshalType.AABB => source.Append(inputExpr, ".AsAABB()"), + MarshalType.Color => source.Append(inputExpr, ".AsColor()"), + MarshalType.Plane => source.Append(inputExpr, ".AsPlane()"), + MarshalType.Callable => source.Append(inputExpr, ".AsCallable()"), + MarshalType.SignalInfo => source.Append(inputExpr, ".AsSignalInfo()"), + MarshalType.Enum => + source.Append("(", typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsInt64()"), + MarshalType.ByteArray => source.Append(inputExpr, ".AsByteArray()"), + MarshalType.Int32Array => source.Append(inputExpr, ".AsInt32Array()"), + MarshalType.Int64Array => source.Append(inputExpr, ".AsInt64Array()"), + MarshalType.Float32Array => source.Append(inputExpr, ".AsFloat32Array()"), + MarshalType.Float64Array => source.Append(inputExpr, ".AsFloat64Array()"), + MarshalType.StringArray => source.Append(inputExpr, ".AsStringArray()"), + MarshalType.Vector2Array => source.Append(inputExpr, ".AsVector2Array()"), + MarshalType.Vector3Array => source.Append(inputExpr, ".AsVector3Array()"), + MarshalType.ColorArray => source.Append(inputExpr, ".AsColorArray()"), + MarshalType.GodotObjectOrDerivedArray => source.Append(inputExpr, ".AsGodotObjectArray<", + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">()"), + MarshalType.SystemArrayOfStringName => source.Append(inputExpr, ".AsSystemArrayOfStringName()"), + MarshalType.SystemArrayOfNodePath => source.Append(inputExpr, ".AsSystemArrayOfNodePath()"), + MarshalType.SystemArrayOfRID => source.Append(inputExpr, ".AsSystemArrayOfRID()"), + MarshalType.Variant => source.Append(inputExpr), + MarshalType.GodotObjectOrDerived => source.Append("(", + typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsGodotObject()"), + MarshalType.StringName => source.Append(inputExpr, ".AsStringName()"), + MarshalType.NodePath => source.Append(inputExpr, ".AsNodePath()"), + MarshalType.RID => source.Append(inputExpr, ".AsRID()"), + MarshalType.GodotDictionary => source.Append(inputExpr, ".AsGodotDictionary()"), + MarshalType.GodotArray => source.Append(inputExpr, ".AsGodotArray()"), + MarshalType.GodotGenericDictionary => source.Append(inputExpr, ".AsGodotDictionary<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">()"), + MarshalType.GodotGenericArray => source.Append(inputExpr, ".AsGodotArray<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">()"), + _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type") + }; + } + + public static StringBuilder AppendManagedToVariantExpr(this StringBuilder source, + string inputExpr, MarshalType marshalType) + { + switch (marshalType) + { + case MarshalType.Boolean: + case MarshalType.Char: + case MarshalType.SByte: + case MarshalType.Int16: + case MarshalType.Int32: + case MarshalType.Int64: + case MarshalType.Byte: + case MarshalType.UInt16: + case MarshalType.UInt32: + case MarshalType.UInt64: + case MarshalType.Single: + case MarshalType.Double: + case MarshalType.String: + case MarshalType.Vector2: + case MarshalType.Vector2i: + case MarshalType.Rect2: + case MarshalType.Rect2i: + case MarshalType.Transform2D: + case MarshalType.Vector3: + case MarshalType.Vector3i: + case MarshalType.Basis: + case MarshalType.Quaternion: + case MarshalType.Transform3D: + case MarshalType.Vector4: + case MarshalType.Vector4i: + case MarshalType.Projection: + case MarshalType.AABB: + case MarshalType.Color: + case MarshalType.Plane: + case MarshalType.Callable: + case MarshalType.SignalInfo: + case MarshalType.ByteArray: + case MarshalType.Int32Array: + case MarshalType.Int64Array: + case MarshalType.Float32Array: + case MarshalType.Float64Array: + case MarshalType.StringArray: + case MarshalType.Vector2Array: + case MarshalType.Vector3Array: + case MarshalType.ColorArray: + case MarshalType.GodotObjectOrDerivedArray: + case MarshalType.SystemArrayOfStringName: + case MarshalType.SystemArrayOfNodePath: + case MarshalType.SystemArrayOfRID: + case MarshalType.GodotObjectOrDerived: + case MarshalType.StringName: + case MarshalType.NodePath: + case MarshalType.RID: + case MarshalType.GodotDictionary: + case MarshalType.GodotArray: + case MarshalType.GodotGenericDictionary: + case MarshalType.GodotGenericArray: + return source.Append("Variant.CreateFrom(", inputExpr, ")"); + case MarshalType.Enum: + return source.Append("Variant.CreateFrom((long)", inputExpr, ")"); + case MarshalType.Variant: + return source.Append(inputExpr); + default: + throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type"); + } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs new file mode 100644 index 0000000000..81c6f2b7d5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Godot.SourceGenerators +{ + internal struct MethodInfo + { + public MethodInfo(string name, PropertyInfo returnVal, MethodFlags flags, + List<PropertyInfo>? arguments, + List<string?>? defaultArguments) + { + Name = name; + ReturnVal = returnVal; + Flags = flags; + Arguments = arguments; + DefaultArguments = defaultArguments; + } + + public string Name { get; } + public PropertyInfo ReturnVal { get; } + public MethodFlags Flags { get; } + public List<PropertyInfo>? Arguments { get; } + public List<string?>? DefaultArguments { get; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs new file mode 100644 index 0000000000..7aaadb27be --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs @@ -0,0 +1,100 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Godot.SourceGenerators +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class MustBeVariantAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics + => ImmutableArray.Create( + Common.GenericTypeArgumentMustBeVariantRule, + Common.GenericTypeParameterMustBeVariantAnnotatedRule, + Common.TypeArgumentParentSymbolUnhandledRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.TypeArgumentList); + } + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var typeArgListSyntax = (TypeArgumentListSyntax)context.Node; + + // Method invocation or variable declaration that contained the type arguments + var parentSyntax = context.Node.Parent; + Debug.Assert(parentSyntax != null); + + var sm = context.SemanticModel; + + var typeCache = new MarshalUtils.TypeCache(context.Compilation); + + for (int i = 0; i < typeArgListSyntax.Arguments.Count; i++) + { + var typeSyntax = typeArgListSyntax.Arguments[i]; + var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol; + Debug.Assert(typeSymbol != null); + + var parentSymbol = sm.GetSymbolInfo(parentSyntax).Symbol; + + if (!ShouldCheckTypeArgument(context, parentSyntax, parentSymbol, typeSyntax, typeSymbol, i)) + { + return; + } + + if (typeSymbol is ITypeParameterSymbol typeParamSymbol) + { + if (!typeParamSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false)) + { + Common.ReportGenericTypeParameterMustBeVariantAnnotated(context, typeSyntax, typeSymbol); + } + continue; + } + + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(typeSymbol, typeCache); + + if (marshalType == null) + { + Common.ReportGenericTypeArgumentMustBeVariant(context, typeSyntax, typeSymbol); + continue; + } + } + } + + /// <summary> + /// Check if the given type argument is being used in a type parameter that contains + /// the <c>MustBeVariantAttribute</c>; otherwise, we ignore the attribute. + /// </summary> + /// <param name="context">Context for a syntax node action.</param> + /// <param name="parentSyntax">The parent node syntax that contains the type node syntax.</param> + /// <param name="parentSymbol">The symbol retrieved for the parent node syntax.</param> + /// <param name="typeArgumentSyntax">The type node syntax of the argument type to check.</param> + /// <param name="typeArgumentSymbol">The symbol retrieved for the type node syntax.</param> + /// <returns><see langword="true"/> if the type must be variant and must be analyzed.</returns> + private bool ShouldCheckTypeArgument(SyntaxNodeAnalysisContext context, SyntaxNode parentSyntax, ISymbol parentSymbol, TypeSyntax typeArgumentSyntax, ITypeSymbol typeArgumentSymbol, int typeArgumentIndex) + { + var typeParamSymbol = parentSymbol switch + { + IMethodSymbol methodSymbol => methodSymbol.TypeParameters[typeArgumentIndex], + INamedTypeSymbol typeSymbol => typeSymbol.TypeParameters[typeArgumentIndex], + _ => null, + }; + + if (typeParamSymbol == null) + { + Common.ReportTypeArgumentParentSymbolUnhandled(context, typeArgumentSyntax, parentSymbol); + return false; + } + + return typeParamSymbol.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs new file mode 100644 index 0000000000..b345f5f84d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs @@ -0,0 +1,23 @@ +namespace Godot.SourceGenerators +{ + internal struct PropertyInfo + { + public PropertyInfo(VariantType type, string name, PropertyHint hint, + string? hintString, PropertyUsageFlags usage, bool exported) + { + Type = type; + Name = name; + Hint = hint; + HintString = hintString; + Usage = usage; + Exported = exported; + } + + public VariantType Type { get; } + public string Name { get; } + public PropertyHint Hint { get; } + public string? HintString { get; } + public PropertyUsageFlags Usage { get; } + public bool Exported { get; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs new file mode 100644 index 0000000000..5ac4f4a47e --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -0,0 +1,408 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptMethodsGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context.Compilation); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private class MethodOverloadEqualityComparer : IEqualityComparer<GodotMethodData> + { + public bool Equals(GodotMethodData x, GodotMethodData y) + => x.ParamTypes.Length == y.ParamTypes.Length && x.Method.Name == y.Method.Name; + + public int GetHashCode(GodotMethodData obj) + { + unchecked + { + return (obj.ParamTypes.Length.GetHashCode() * 397) ^ obj.Method.Name.GetHashCode(); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptMethods_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var members = symbol.GetMembers(); + + var methodSymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared) + .Cast<IMethodSymbol>() + .Where(m => m.MethodKind == MethodKind.Ordinary); + + var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache) + .Distinct(new MethodOverloadEqualityComparer()) + .ToArray(); + + source.Append(" private partial class GodotInternal {\n"); + + // Generate cached StringNames for methods and properties, for fast lookup + + var distinctMethodNames = godotClassMethods + .Select(m => m.Method.Name) + .Distinct() + .ToArray(); + + foreach (string methodName in distinctMethodNames) + { + source.Append(" public static readonly StringName MethodName_"); + source.Append(methodName); + source.Append(" = \""); + source.Append(methodName); + source.Append("\";\n"); + } + + source.Append(" }\n"); // class GodotInternal + + // Generate GetGodotMethodList + + if (godotClassMethods.Length > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + + source.Append(" internal new static ") + .Append(listType) + .Append(" GetGodotMethodList()\n {\n"); + + source.Append(" var methods = new ") + .Append(listType) + .Append("(") + .Append(godotClassMethods.Length) + .Append(");\n"); + + foreach (var method in godotClassMethods) + { + var methodInfo = DetermineMethodInfo(method); + AppendMethodInfo(source, methodInfo); + } + + source.Append(" return methods;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + // Generate InvokeGodotClassMethod + + if (godotClassMethods.Length > 0) + { + source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, "); + source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n"); + + foreach (var method in godotClassMethods) + { + GenerateMethodInvoker(method, source); + } + + source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n"); + + source.Append(" }\n"); + } + + // Generate HasGodotClassMethod + + if (distinctMethodNames.Length > 0) + { + source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n"); + + bool isFirstEntry = true; + foreach (string methodName in distinctMethodNames) + { + GenerateHasMethodEntry(methodName, source, isFirstEntry); + isFirstEntry = false; + } + + source.Append(" return base.HasGodotClassMethod(method);\n"); + + source.Append(" }\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) + { + source.Append(" methods.Add(new(name: GodotInternal.MethodName_") + .Append(methodInfo.Name) + .Append(", returnVal: "); + + AppendPropertyInfo(source, methodInfo.ReturnVal); + + source.Append(", flags: (Godot.MethodFlags)") + .Append((int)methodInfo.Flags) + .Append(", arguments: "); + + if (methodInfo.Arguments is { Count: > 0 }) + { + source.Append("new() { "); + + foreach (var param in methodInfo.Arguments) + { + AppendPropertyInfo(source, param); + + // C# allows colon after the last element + source.Append(", "); + } + + source.Append(" }"); + } + else + { + source.Append("null"); + } + + source.Append(", defaultArguments: null));\n"); + } + + private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) + { + source.Append("new(type: (Godot.Variant.Type)") + .Append((int)propertyInfo.Type) + .Append(", name: \"") + .Append(propertyInfo.Name) + .Append("\", hint: (Godot.PropertyHint)") + .Append((int)propertyInfo.Hint) + .Append(", hintString: \"") + .Append(propertyInfo.HintString) + .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append((int)propertyInfo.Usage) + .Append(", exported: ") + .Append(propertyInfo.Exported ? "true" : "false") + .Append(")"); + } + + private static MethodInfo DetermineMethodInfo(GodotMethodData method) + { + PropertyInfo returnVal; + + if (method.RetType != null) + { + returnVal = DeterminePropertyInfo(method.RetType.Value, name: string.Empty); + } + else + { + returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None, + hintString: null, PropertyUsageFlags.Default, exported: false); + } + + int paramCount = method.ParamTypes.Length; + + List<PropertyInfo>? arguments; + + if (paramCount > 0) + { + arguments = new(capacity: paramCount); + + for (int i = 0; i < paramCount; i++) + { + arguments.Add(DeterminePropertyInfo(method.ParamTypes[i], + name: method.Method.Parameters[i].Name)); + } + } + else + { + arguments = null; + } + + return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments, + defaultArguments: null); + } + + private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name) + { + var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; + + var propUsage = PropertyUsageFlags.Default; + + if (memberVariantType == VariantType.Nil) + propUsage |= PropertyUsageFlags.NilIsVariant; + + return new PropertyInfo(memberVariantType, name, + PropertyHint.None, string.Empty, propUsage, exported: false); + } + + private static void GenerateHasMethodEntry( + string methodName, + StringBuilder source, + bool isFirstEntry + ) + { + source.Append(" "); + if (!isFirstEntry) + source.Append("else "); + source.Append("if (method == GodotInternal.MethodName_"); + source.Append(methodName); + source.Append(") {\n return true;\n }\n"); + } + + private static void GenerateMethodInvoker( + GodotMethodData method, + StringBuilder source + ) + { + string methodName = method.Method.Name; + + source.Append(" if (method == GodotInternal.MethodName_"); + source.Append(methodName); + source.Append(" && argCount == "); + source.Append(method.ParamTypes.Length); + source.Append(") {\n"); + + if (method.RetType != null) + source.Append(" var callRet = "); + else + source.Append(" "); + + source.Append(methodName); + source.Append("("); + + for (int i = 0; i < method.ParamTypes.Length; i++) + { + if (i != 0) + source.Append(", "); + + source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"), + method.ParamTypeSymbols[i], method.ParamTypes[i]); + } + + source.Append(");\n"); + + if (method.RetType != null) + { + source.Append(" ret = "); + + source.AppendManagedToNativeVariantExpr("callRet", method.RetType.Value); + source.Append(";\n"); + + source.Append(" return true;\n"); + } + else + { + source.Append(" ret = default;\n"); + source.Append(" return true;\n"); + } + + source.Append(" }\n"); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index fa65595290..e8a9e28d0c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -14,13 +14,13 @@ namespace Godot.SourceGenerators { public void Execute(GeneratorExecutionContext context) { - if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle) - && toggle == "disabled") - { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + if (context.IsGodotToolsProject()) return; - } - // NOTE: IsNullOrEmpty doesn't work well with nullable checks + // NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0 // ReSharper disable once ReplaceWithStringIsNullOrEmpty if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir) || godotProjectDir!.Length == 0) @@ -28,17 +28,18 @@ namespace Godot.SourceGenerators throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); } - var godotClasses = context.Compilation.SyntaxTrees + Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context + .Compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes() .OfType<ClassDeclarationSyntax>() // Ignore inner classes - .Where(cds => !(cds.Parent is ClassDeclarationSyntax)) + .Where(cds => !cds.IsNested()) .SelectGodotScriptClasses(context.Compilation) // Report and skip non-partial classes .Where(x => { - if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute()) + if (x.cds.IsPartial()) return true; Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); return false; @@ -89,21 +90,14 @@ namespace Godot.SourceGenerators attributes.Append(@""")]"); } - string className = symbol.Name; - INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? namespaceSymbol.FullQualifiedName() : string.Empty; bool hasNamespace = classNs.Length != 0; - var uniqueName = new StringBuilder(); - if (hasNamespace) - uniqueName.Append($"{classNs}."); - uniqueName.Append(className); - if (symbol.IsGenericType) - uniqueName.Append($"Of{string.Join(string.Empty, symbol.TypeParameters)}"); - uniqueName.Append("_ScriptPath_Generated"); + var uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptPath_Generated"; var source = new StringBuilder(); @@ -123,10 +117,8 @@ namespace Godot.SourceGenerators } source.Append(attributes); - source.Append("\n partial class "); - source.Append(className); - if (symbol.IsGenericType) - source.Append($"<{string.Join(", ", symbol.TypeParameters)}>"); + source.Append("\npartial class "); + source.Append(symbol.NameWithTypeParameters()); source.Append("\n{\n}\n"); if (hasNamespace) @@ -134,7 +126,7 @@ namespace Godot.SourceGenerators source.Append("\n}\n"); } - context.AddSource(uniqueName.ToString(), SourceText.From(source.ToString(), Encoding.UTF8)); + context.AddSource(uniqueHint.ToString(), SourceText.From(source.ToString(), Encoding.UTF8)); } private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context, diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs new file mode 100644 index 0000000000..0c9b17d69a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -0,0 +1,664 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptPropertiesGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context.Compilation); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptProperties_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var members = symbol.GetMembers(); + + var propertySymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property) + .Cast<IPropertySymbol>(); + + var fieldSymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>(); + + var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + + source.Append(" private partial class GodotInternal {\n"); + + // Generate cached StringNames for methods and properties, for fast lookup + + foreach (var property in godotClassProperties) + { + string propertyName = property.PropertySymbol.Name; + source.Append(" public static readonly StringName PropName_"); + source.Append(propertyName); + source.Append(" = \""); + source.Append(propertyName); + source.Append("\";\n"); + } + + foreach (var field in godotClassFields) + { + string fieldName = field.FieldSymbol.Name; + source.Append(" public static readonly StringName PropName_"); + source.Append(fieldName); + source.Append(" = \""); + source.Append(fieldName); + source.Append("\";\n"); + } + + source.Append(" }\n"); // class GodotInternal + + if (godotClassProperties.Length > 0 || godotClassFields.Length > 0) + { + bool isFirstEntry; + + // Generate SetGodotClassPropertyValue + + bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) && + godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly); + + if (!allPropertiesAreReadOnly) + { + source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, "); + source.Append("in godot_variant value)\n {\n"); + + isFirstEntry = true; + foreach (var property in godotClassProperties) + { + if (property.PropertySymbol.IsReadOnly) + continue; + + GeneratePropertySetter(property.PropertySymbol.Name, + property.PropertySymbol.Type, property.Type, source, isFirstEntry); + isFirstEntry = false; + } + + foreach (var field in godotClassFields) + { + if (field.FieldSymbol.IsReadOnly) + continue; + + GeneratePropertySetter(field.FieldSymbol.Name, + field.FieldSymbol.Type, field.Type, source, isFirstEntry); + isFirstEntry = false; + } + + source.Append(" return base.SetGodotClassPropertyValue(name, value);\n"); + + source.Append(" }\n"); + } + + // Generate GetGodotClassPropertyValue + + source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, "); + source.Append("out godot_variant value)\n {\n"); + + isFirstEntry = true; + foreach (var property in godotClassProperties) + { + GeneratePropertyGetter(property.PropertySymbol.Name, + property.Type, source, isFirstEntry); + isFirstEntry = false; + } + + foreach (var field in godotClassFields) + { + GeneratePropertyGetter(field.FieldSymbol.Name, + field.Type, source, isFirstEntry); + isFirstEntry = false; + } + + source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n"); + + source.Append(" }\n"); + + // Generate GetGodotPropertyList + + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + + source.Append(" internal new static ") + .Append(dictionaryType) + .Append(" GetGodotPropertyList()\n {\n"); + + source.Append(" var properties = new ") + .Append(dictionaryType) + .Append("();\n"); + + foreach (var property in godotClassProperties) + { + foreach (var groupingInfo in DetermineGroupingPropertyInfo(property.PropertySymbol)) + AppendGroupingPropertyInfo(source, groupingInfo); + + var propertyInfo = DeterminePropertyInfo(context, typeCache, + property.PropertySymbol, property.Type); + + if (propertyInfo == null) + continue; + + AppendPropertyInfo(source, propertyInfo.Value); + } + + foreach (var field in godotClassFields) + { + + foreach (var groupingInfo in DetermineGroupingPropertyInfo(field.FieldSymbol)) + AppendGroupingPropertyInfo(source, groupingInfo); + + var propertyInfo = DeterminePropertyInfo(context, typeCache, + field.FieldSymbol, field.Type); + + if (propertyInfo == null) + continue; + + AppendPropertyInfo(source, propertyInfo.Value); + } + + source.Append(" return properties;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static void GeneratePropertySetter( + string propertyMemberName, + ITypeSymbol propertyTypeSymbol, + MarshalType propertyMarshalType, + StringBuilder source, + bool isFirstEntry + ) + { + source.Append(" "); + + if (!isFirstEntry) + source.Append("else "); + + source.Append("if (name == GodotInternal.PropName_") + .Append(propertyMemberName) + .Append(") {\n") + .Append(" ") + .Append(propertyMemberName) + .Append(" = ") + .AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType) + .Append(";\n") + .Append(" return true;\n") + .Append(" }\n"); + } + + private static void GeneratePropertyGetter( + string propertyMemberName, + MarshalType propertyMarshalType, + StringBuilder source, + bool isFirstEntry + ) + { + source.Append(" "); + + if (!isFirstEntry) + source.Append("else "); + + source.Append("if (name == GodotInternal.PropName_") + .Append(propertyMemberName) + .Append(") {\n") + .Append(" value = ") + .AppendManagedToNativeVariantExpr(propertyMemberName, propertyMarshalType) + .Append(";\n") + .Append(" return true;\n") + .Append(" }\n"); + } + + private static void AppendGroupingPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) + { + source.Append(" properties.Add(new(type: (Godot.Variant.Type)") + .Append((int)VariantType.Nil) + .Append(", name: \"") + .Append(propertyInfo.Name) + .Append("\", hint: (Godot.PropertyHint)") + .Append((int)PropertyHint.None) + .Append(", hintString: \"") + .Append(propertyInfo.HintString) + .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append((int)propertyInfo.Usage) + .Append(", exported: true));\n"); + } + + private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) + { + source.Append(" properties.Add(new(type: (Godot.Variant.Type)") + .Append((int)propertyInfo.Type) + .Append(", name: GodotInternal.PropName_") + .Append(propertyInfo.Name) + .Append(", hint: (Godot.PropertyHint)") + .Append((int)propertyInfo.Hint) + .Append(", hintString: \"") + .Append(propertyInfo.HintString) + .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append((int)propertyInfo.Usage) + .Append(", exported: ") + .Append(propertyInfo.Exported ? "true" : "false") + .Append("));\n"); + } + + private static IEnumerable<PropertyInfo> DetermineGroupingPropertyInfo(ISymbol memberSymbol) + { + foreach (var attr in memberSymbol.GetAttributes()) + { + PropertyUsageFlags? propertyUsage = attr.AttributeClass?.ToString() switch + { + GodotClasses.ExportCategoryAttr => PropertyUsageFlags.Category, + GodotClasses.ExportGroupAttr => PropertyUsageFlags.Group, + GodotClasses.ExportSubgroupAttr => PropertyUsageFlags.Subgroup, + _ => null + }; + + if (propertyUsage is null) + continue; + + if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string name) + { + string? hintString = null; + if (propertyUsage != PropertyUsageFlags.Category && attr.ConstructorArguments.Length > 1) + hintString = attr.ConstructorArguments[1].Value?.ToString(); + + yield return new PropertyInfo(VariantType.Nil, name, PropertyHint.None, hintString, propertyUsage.Value, true); + } + } + } + + private static PropertyInfo? DeterminePropertyInfo( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + ISymbol memberSymbol, + MarshalType marshalType + ) + { + var exportAttr = memberSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false); + + var propertySymbol = memberSymbol as IPropertySymbol; + var fieldSymbol = memberSymbol as IFieldSymbol; + + if (exportAttr != null && propertySymbol != null) + { + if (propertySymbol.GetMethod == null) + { + // This should never happen, as we filtered WriteOnly properties, but just in case. + Common.ReportExportedMemberIsWriteOnly(context, propertySymbol); + return null; + } + + if (propertySymbol.SetMethod == null) + { + // This should never happen, as we filtered ReadOnly properties, but just in case. + Common.ReportExportedMemberIsReadOnly(context, propertySymbol); + return null; + } + } + + var memberType = propertySymbol?.Type ?? fieldSymbol!.Type; + + var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; + string memberName = memberSymbol.Name; + + if (exportAttr == null) + { + return new PropertyInfo(memberVariantType, memberName, PropertyHint.None, + hintString: null, PropertyUsageFlags.ScriptVariable, exported: false); + } + + if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType, + isTypeArgument: false, out var hint, out var hintString)) + { + var constructorArguments = exportAttr.ConstructorArguments; + + if (constructorArguments.Length > 0) + { + var hintValue = exportAttr.ConstructorArguments[0].Value; + + hint = hintValue switch + { + null => PropertyHint.None, + int intValue => (PropertyHint)intValue, + _ => (PropertyHint)(long)hintValue + }; + + hintString = constructorArguments.Length > 1 ? + exportAttr.ConstructorArguments[1].Value?.ToString() : + null; + } + else + { + hint = PropertyHint.None; + } + } + + var propUsage = PropertyUsageFlags.Default | PropertyUsageFlags.ScriptVariable; + + if (memberVariantType == VariantType.Nil) + propUsage |= PropertyUsageFlags.NilIsVariant; + + return new PropertyInfo(memberVariantType, memberName, + hint, hintString, propUsage, exported: true); + } + + private static bool TryGetMemberExportHint( + MarshalUtils.TypeCache typeCache, + ITypeSymbol type, AttributeData exportAttr, + VariantType variantType, bool isTypeArgument, + out PropertyHint hint, out string? hintString + ) + { + hint = PropertyHint.None; + hintString = null; + + if (variantType == VariantType.Nil) + return true; // Variant, no export hint + + if (variantType == VariantType.Int && + type.IsValueType && type.TypeKind == TypeKind.Enum) + { + bool hasFlagsAttr = type.GetAttributes() + .Any(a => a.AttributeClass?.IsSystemFlagsAttribute() ?? false); + + hint = hasFlagsAttr ? PropertyHint.Flags : PropertyHint.Enum; + + var members = type.GetMembers(); + + var enumFields = members + .Where(s => s.Kind == SymbolKind.Field && s.IsStatic && + s.DeclaredAccessibility == Accessibility.Public && + !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>().ToArray(); + + var hintStringBuilder = new StringBuilder(); + var nameOnlyHintStringBuilder = new StringBuilder(); + + // True: enum Foo { Bar, Baz, Qux } + // True: enum Foo { Bar = 0, Baz = 1, Qux = 2 } + // False: enum Foo { Bar = 0, Baz = 7, Qux = 5 } + bool usesDefaultValues = true; + + for (int i = 0; i < enumFields.Length; i++) + { + var enumField = enumFields[i]; + + if (i > 0) + { + hintStringBuilder.Append(","); + nameOnlyHintStringBuilder.Append(","); + } + + string enumFieldName = enumField.Name; + hintStringBuilder.Append(enumFieldName); + nameOnlyHintStringBuilder.Append(enumFieldName); + + long val = enumField.ConstantValue switch + { + sbyte v => v, + short v => v, + int v => v, + long v => v, + byte v => v, + ushort v => v, + uint v => v, + ulong v => (long)v, + _ => 0 + }; + + uint expectedVal = (uint)(hint == PropertyHint.Flags ? 1 << i : i); + if (val != expectedVal) + usesDefaultValues = false; + + hintStringBuilder.Append(":"); + hintStringBuilder.Append(val); + } + + hintString = !usesDefaultValues ? + hintStringBuilder.ToString() : + // If we use the format NAME:VAL, that's what the editor displays. + // That's annoying if the user is not using custom values for the enum constants. + // This may not be needed in the future if the editor is changed to not display values. + nameOnlyHintStringBuilder.ToString(); + + return true; + } + + if (variantType == VariantType.Object && type is INamedTypeSymbol memberNamedType) + { + if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Resource")) + { + string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!; + + hint = PropertyHint.ResourceType; + hintString = nativeTypeName; + + return true; + } + + if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Node")) + { + string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!; + + hint = PropertyHint.NodeType; + hintString = nativeTypeName; + + return true; + } + } + + static bool GetStringArrayEnumHint(VariantType elementVariantType, + AttributeData exportAttr, out string? hintString) + { + var constructorArguments = exportAttr.ConstructorArguments; + + if (constructorArguments.Length > 0) + { + var presetHintValue = exportAttr.ConstructorArguments[0].Value; + + PropertyHint presetHint = presetHintValue switch + { + null => PropertyHint.None, + int intValue => (PropertyHint)intValue, + _ => (PropertyHint)(long)presetHintValue + }; + + if (presetHint == PropertyHint.Enum) + { + string? presetHintString = constructorArguments.Length > 1 ? + exportAttr.ConstructorArguments[1].Value?.ToString() : + null; + + hintString = (int)elementVariantType + "/" + (int)PropertyHint.Enum + ":"; + + if (presetHintString != null) + hintString += presetHintString; + + return true; + } + } + + hintString = null; + return false; + } + + if (!isTypeArgument && variantType == VariantType.Array) + { + var elementType = MarshalUtils.GetArrayElementType(type); + + if (elementType == null) + return false; // Non-generic Array, so there's no hint to add + + var elementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementType, typeCache)!.Value; + var elementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(elementMarshalType)!.Value; + + bool isPresetHint = false; + + if (elementVariantType == VariantType.String) + isPresetHint = GetStringArrayEnumHint(elementVariantType, exportAttr, out hintString); + + if (!isPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementType, + exportAttr, elementVariantType, isTypeArgument: true, + out var elementHint, out var elementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + hintString = (int)elementVariantType + "/" + (int)elementHint + ":"; + + if (elementHintString != null) + hintString += elementHintString; + } + else + { + hintString = (int)elementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + hint = PropertyHint.TypeString; + + return hintString != null; + } + + if (!isTypeArgument && variantType == VariantType.PackedStringArray) + { + if (GetStringArrayEnumHint(VariantType.String, exportAttr, out hintString)) + { + hint = PropertyHint.TypeString; + return true; + } + } + + if (!isTypeArgument && variantType == VariantType.Dictionary) + { + // TODO: Dictionaries are not supported in the inspector + return false; + } + + return false; + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs new file mode 100644 index 0000000000..85fadaa52e --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -0,0 +1,293 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptPropertyDefValGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context.Compilation); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptPropertyDefVal_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var exportedMembers = new List<ExportedPropertyMetadata>(); + + var members = symbol.GetMembers(); + + var exportedProperties = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property) + .Cast<IPropertySymbol>() + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false)) + .ToArray(); + + var exportedFields = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>() + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false)) + .ToArray(); + + foreach (var property in exportedProperties) + { + if (property.IsStatic) + { + Common.ReportExportedMemberIsStatic(context, property); + continue; + } + + // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (property.IsWriteOnly) + { + Common.ReportExportedMemberIsWriteOnly(context, property); + continue; + } + + if (property.IsReadOnly) + { + Common.ReportExportedMemberIsReadOnly(context, property); + continue; + } + + + var propertyType = property.Type; + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(propertyType, typeCache); + + if (marshalType == null) + { + Common.ReportExportedMemberTypeNotSupported(context, property); + continue; + } + + // TODO: Detect default value from simple property getters (currently we only detect from initializers) + + EqualsValueClauseSyntax? initializer = property.DeclaringSyntaxReferences + .Select(r => r.GetSyntax() as PropertyDeclarationSyntax) + .Select(s => s?.Initializer ?? null) + .FirstOrDefault(); + + string? value = initializer?.Value.ToString(); + + exportedMembers.Add(new ExportedPropertyMetadata( + property.Name, marshalType.Value, propertyType, value)); + } + + foreach (var field in exportedFields) + { + if (field.IsStatic) + { + Common.ReportExportedMemberIsStatic(context, field); + continue; + } + + // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (field.IsReadOnly) + { + Common.ReportExportedMemberIsReadOnly(context, field); + continue; + } + + var fieldType = field.Type; + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(fieldType, typeCache); + + if (marshalType == null) + { + Common.ReportExportedMemberTypeNotSupported(context, field); + continue; + } + + EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType<VariableDeclaratorSyntax>() + .Select(s => s.Initializer) + .FirstOrDefault(i => i != null); + + string? value = initializer?.Value.ToString(); + + exportedMembers.Add(new ExportedPropertyMetadata( + field.Name, marshalType.Value, fieldType, value)); + } + + // Generate GetGodotExportedProperties + + if (exportedMembers.Count > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + string dictionaryType = "System.Collections.Generic.Dictionary<StringName, object>"; + + source.Append("#if TOOLS\n"); + source.Append(" internal new static "); + source.Append(dictionaryType); + source.Append(" GetGodotPropertyDefaultValues()\n {\n"); + + source.Append(" var values = new "); + source.Append(dictionaryType); + source.Append("("); + source.Append(exportedMembers.Count); + source.Append(");\n"); + + foreach (var exportedMember in exportedMembers) + { + string defaultValueLocalName = string.Concat("__", exportedMember.Name, "_default_value"); + + source.Append(" "); + source.Append(exportedMember.TypeSymbol.FullQualifiedName()); + source.Append(" "); + source.Append(defaultValueLocalName); + source.Append(" = "); + source.Append(exportedMember.Value ?? "default"); + source.Append(";\n"); + source.Append(" values.Add(GodotInternal.PropName_"); + source.Append(exportedMember.Name); + source.Append(", "); + source.Append(defaultValueLocalName); + source.Append(");\n"); + } + + source.Append(" return values;\n"); + source.Append(" }\n"); + source.Append("#endif\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private struct ExportedPropertyMetadata + { + public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value) + { + Name = name; + Type = type; + TypeSymbol = typeSymbol; + Value = value; + } + + public string Name { get; } + public MarshalType Type { get; } + public ITypeSymbol TypeSymbol { get; } + public string? Value { get; } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs new file mode 100644 index 0000000000..ec04a319e2 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + // Placeholder. Once we switch to native extensions this will act as the registrar for all + // user Godot classes in the assembly. Think of it as something similar to `register_types`. + public class ScriptRegistrarGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + throw new System.NotImplementedException(); + } + + public void Execute(GeneratorExecutionContext context) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs new file mode 100644 index 0000000000..5a84122c4c --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -0,0 +1,283 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptSerializationGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context.Compilation); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptSerialization_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var members = symbol.GetMembers(); + + var propertySymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property) + .Cast<IPropertySymbol>(); + + var fieldSymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>(); + + var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + + var signalDelegateSymbols = members + .Where(s => s.Kind == SymbolKind.NamedType) + .Cast<INamedTypeSymbol>() + .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate) + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false)); + + List<GodotSignalDelegateData> godotSignalDelegates = new(); + + foreach (var signalDelegateSymbol in signalDelegateSymbols) + { + if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix)) + continue; + + string signalName = signalDelegateSymbol.Name; + signalName = signalName.Substring(0, + signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length); + + var invokeMethodData = signalDelegateSymbol + .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache); + + if (invokeMethodData == null) + continue; + + godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); + } + + source.Append( + " protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); + source.Append(" base.SaveGodotObjectData(info);\n"); + + // Save properties + + foreach (var property in godotClassProperties) + { + string propertyName = property.PropertySymbol.Name; + + source.Append(" info.AddProperty(GodotInternal.PropName_") + .Append(propertyName) + .Append(", ") + .AppendManagedToVariantExpr(string.Concat("this.", propertyName), property.Type) + .Append(");\n"); + } + + // Save fields + + foreach (var field in godotClassFields) + { + string fieldName = field.FieldSymbol.Name; + + source.Append(" info.AddProperty(GodotInternal.PropName_") + .Append(fieldName) + .Append(", ") + .AppendManagedToVariantExpr(string.Concat("this.", fieldName), field.Type) + .Append(");\n"); + } + + // Save signal events + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + + source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_") + .Append(signalName) + .Append(", this.backing_") + .Append(signalName) + .Append(");\n"); + } + + source.Append(" }\n"); + + source.Append( + " protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); + source.Append(" base.RestoreGodotObjectData(info);\n"); + + // Restore properties + + foreach (var property in godotClassProperties) + { + string propertyName = property.PropertySymbol.Name; + + source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + .Append(propertyName) + .Append(", out var _value_") + .Append(propertyName) + .Append("))\n") + .Append(" this.") + .Append(propertyName) + .Append(" = ") + .AppendVariantToManagedExpr(string.Concat("_value_", propertyName), + property.PropertySymbol.Type, property.Type) + .Append(";\n"); + } + + // Restore fields + + foreach (var field in godotClassFields) + { + string fieldName = field.FieldSymbol.Name; + + source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + .Append(fieldName) + .Append(", out var _value_") + .Append(fieldName) + .Append("))\n") + .Append(" this.") + .Append(fieldName) + .Append(" = ") + .AppendVariantToManagedExpr(string.Concat("_value_", fieldName), + field.FieldSymbol.Type, field.Type) + .Append(";\n"); + } + + // Restore signal events + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName(); + + source.Append(" if (info.TryGetSignalEventDelegate<") + .Append(signalDelegateQualifiedName) + .Append(">(GodotInternal.SignalName_") + .Append(signalName) + .Append(", out var _value_") + .Append(signalName) + .Append("))\n") + .Append(" this.backing_") + .Append(signalName) + .Append(" = _value_") + .Append(signalName) + .Append(";\n"); + } + + source.Append(" }\n"); + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs new file mode 100644 index 0000000000..161834a4be --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -0,0 +1,432 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +// TODO: +// Determine a proper way to emit the signal. +// 'Emit(nameof(TheEvent))' creates a StringName everytime and has the overhead of string marshaling. +// I haven't decided on the best option yet. Some possibilities: +// - Expose the generated StringName fields to the user, for use with 'Emit(...)'. +// - Generate a 'EmitSignalName' method for each event signal. + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptSignalsGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context.Compilation); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + internal static string SignalDelegateSuffix = "EventHandler"; + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptSignals_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + // TODO: + // The delegate name already needs to end with 'Signal' to avoid collision with the event name. + // Requiring SignalAttribute is redundant. Should we remove it to make declaration shorter? + + var members = symbol.GetMembers(); + + var signalDelegateSymbols = members + .Where(s => s.Kind == SymbolKind.NamedType) + .Cast<INamedTypeSymbol>() + .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate) + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false)); + + List<GodotSignalDelegateData> godotSignalDelegates = new(); + + foreach (var signalDelegateSymbol in signalDelegateSymbols) + { + if (!signalDelegateSymbol.Name.EndsWith(SignalDelegateSuffix)) + { + Common.ReportSignalDelegateMissingSuffix(context, signalDelegateSymbol); + continue; + } + + string signalName = signalDelegateSymbol.Name; + signalName = signalName.Substring(0, signalName.Length - SignalDelegateSuffix.Length); + + var invokeMethodData = signalDelegateSymbol + .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache); + + if (invokeMethodData == null) + { + if (signalDelegateSymbol.DelegateInvokeMethod is IMethodSymbol methodSymbol) + { + foreach (var parameter in methodSymbol.Parameters) + { + if (parameter.RefKind != RefKind.None) + { + Common.ReportSignalParameterTypeNotSupported(context, parameter); + continue; + } + + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(parameter.Type, typeCache); + + if (marshalType == null) + { + Common.ReportSignalParameterTypeNotSupported(context, parameter); + } + } + + if (!methodSymbol.ReturnsVoid) + { + Common.ReportSignalDelegateSignatureMustReturnVoid(context, signalDelegateSymbol); + } + } + continue; + } + + godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); + } + + source.Append(" private partial class GodotInternal {\n"); + + // Generate cached StringNames for methods and properties, for fast lookup + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + source.Append(" public static readonly StringName SignalName_"); + source.Append(signalName); + source.Append(" = \""); + source.Append(signalName); + source.Append("\";\n"); + } + + source.Append(" }\n"); // class GodotInternal + + // Generate GetGodotSignalList + + if (godotSignalDelegates.Count > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + + source.Append(" internal new static ") + .Append(listType) + .Append(" GetGodotSignalList()\n {\n"); + + source.Append(" var signals = new ") + .Append(listType) + .Append("(") + .Append(godotSignalDelegates.Count) + .Append(");\n"); + + foreach (var signalDelegateData in godotSignalDelegates) + { + var methodInfo = DetermineMethodInfo(signalDelegateData); + AppendMethodInfo(source, methodInfo); + } + + source.Append(" return signals;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + // Generate signal event + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + + // TODO: Hide backing event from code-completion and debugger + // The reason we have a backing field is to hide the invoke method from the event, + // as it doesn't emit the signal, only the event delegates. This can confuse users. + // Maybe we should directly connect the delegates, as we do with native signals? + source.Append(" private ") + .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(" backing_") + .Append(signalName) + .Append(";\n"); + + source.Append(" public event ") + .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(" ") + .Append(signalName) + .Append(" {\n") + .Append(" add => backing_") + .Append(signalName) + .Append(" += value;\n") + .Append(" remove => backing_") + .Append(signalName) + .Append(" -= value;\n") + .Append("}\n"); + } + + // Generate RaiseGodotClassSignalCallbacks + + if (godotSignalDelegates.Count > 0) + { + source.Append( + " protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, "); + source.Append("NativeVariantPtrArgs args, int argCount)\n {\n"); + + foreach (var signal in godotSignalDelegates) + { + GenerateSignalEventInvoker(signal, source); + } + + source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n"); + + source.Append(" }\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) + { + source.Append(" signals.Add(new(name: GodotInternal.SignalName_") + .Append(methodInfo.Name) + .Append(", returnVal: "); + + AppendPropertyInfo(source, methodInfo.ReturnVal); + + source.Append(", flags: (Godot.MethodFlags)") + .Append((int)methodInfo.Flags) + .Append(", arguments: "); + + if (methodInfo.Arguments is { Count: > 0 }) + { + source.Append("new() { "); + + foreach (var param in methodInfo.Arguments) + { + AppendPropertyInfo(source, param); + + // C# allows colon after the last element + source.Append(", "); + } + + source.Append(" }"); + } + else + { + source.Append("null"); + } + + source.Append(", defaultArguments: null));\n"); + } + + private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) + { + source.Append("new(type: (Godot.Variant.Type)") + .Append((int)propertyInfo.Type) + .Append(", name: \"") + .Append(propertyInfo.Name) + .Append("\", hint: (Godot.PropertyHint)") + .Append((int)propertyInfo.Hint) + .Append(", hintString: \"") + .Append(propertyInfo.HintString) + .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append((int)propertyInfo.Usage) + .Append(", exported: ") + .Append(propertyInfo.Exported ? "true" : "false") + .Append(")"); + } + + private static MethodInfo DetermineMethodInfo(GodotSignalDelegateData signalDelegateData) + { + var invokeMethodData = signalDelegateData.InvokeMethodData; + + PropertyInfo returnVal; + + if (invokeMethodData.RetType != null) + { + returnVal = DeterminePropertyInfo(invokeMethodData.RetType.Value, name: string.Empty); + } + else + { + returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None, + hintString: null, PropertyUsageFlags.Default, exported: false); + } + + int paramCount = invokeMethodData.ParamTypes.Length; + + List<PropertyInfo>? arguments; + + if (paramCount > 0) + { + arguments = new(capacity: paramCount); + + for (int i = 0; i < paramCount; i++) + { + arguments.Add(DeterminePropertyInfo(invokeMethodData.ParamTypes[i], + name: invokeMethodData.Method.Parameters[i].Name)); + } + } + else + { + arguments = null; + } + + return new MethodInfo(signalDelegateData.Name, returnVal, MethodFlags.Default, arguments, + defaultArguments: null); + } + + private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name) + { + var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; + + var propUsage = PropertyUsageFlags.Default; + + if (memberVariantType == VariantType.Nil) + propUsage |= PropertyUsageFlags.NilIsVariant; + + return new PropertyInfo(memberVariantType, name, + PropertyHint.None, string.Empty, propUsage, exported: false); + } + + private static void GenerateSignalEventInvoker( + GodotSignalDelegateData signal, + StringBuilder source + ) + { + string signalName = signal.Name; + var invokeMethodData = signal.InvokeMethodData; + + source.Append(" if (signal == GodotInternal.SignalName_"); + source.Append(signalName); + source.Append(" && argCount == "); + source.Append(invokeMethodData.ParamTypes.Length); + source.Append(") {\n"); + source.Append(" backing_"); + source.Append(signalName); + source.Append("?.Invoke("); + + for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++) + { + if (i != 0) + source.Append(", "); + + source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"), + invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]); + } + + source.Append(");\n"); + + source.Append(" return;\n"); + + source.Append(" }\n"); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 2bf1cb7a18..01aa65bfc3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -7,8 +7,6 @@ namespace GodotTools.BuildLogger { public class GodotBuildLogger : ILogger { - public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location); - public string Parameters { get; set; } public LoggerVerbosity Verbosity { get; set; } diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index 0afec970c6..9e36497b06 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -5,6 +5,6 @@ <LangVersion>7.2</LangVersion> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" /> + <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" ExcludeAssets="runtime" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index d6d8962f90..cfd5c88a58 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid> - <TargetFramework>netstandard2.0</TargetFramework> - <LangVersion>7.2</LangVersion> + <TargetFramework>net6.0</TargetFramework> + <LangVersion>10</LangVersion> </PropertyGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj index 303ca3a293..d2132115f3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid> <OutputType>Exe</OutputType> diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj index 5b3ed0b1b7..c05096bdcc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid> <OutputType>Exe</OutputType> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 37123ba2b2..bde14b2b40 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -1,32 +1,16 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> - <TargetFramework>net472</TargetFramework> - <LangVersion>7.2</LangVersion> + <TargetFramework>net6.0</TargetFramework> + <LangVersion>10</LangVersion> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Build" Version="16.5.0" /> + <PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" /> + <PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> <ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" /> </ItemGroup> - <!-- - The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described - here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486 - We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when - searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed. - --> - <ItemGroup> - <None Include="MSBuild.exe" CopyToOutputDirectory="Always" /> - </ItemGroup> - <Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) "> - <PropertyGroup> - <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> - <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir> - </PropertyGroup> - <!-- Need to copy it here as well on Windows --> - <Copy SourceFiles="MSBuild.exe" DestinationFiles="$(GodotOutputDataDir)\Mono\lib\mono\v4.0\MSBuild.exe" /> - </Target> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe deleted file mode 100644 index e69de29bb2..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe +++ /dev/null diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 7d49d251dd..fb6d2a707b 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Text; using Microsoft.Build.Construction; @@ -21,7 +22,8 @@ namespace GodotTools.ProjectEditor root.Sdk = GodotSdkAttrValue; var mainGroup = root.AddPropertyGroup(); - mainGroup.AddProperty("TargetFramework", "netstandard2.1"); + mainGroup.AddProperty("TargetFramework", "net6.0"); + mainGroup.AddProperty("EnableDynamicLoading", "true"); string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true); @@ -44,7 +46,7 @@ namespace GodotTools.ProjectEditor // Save (without BOM) root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); - return Guid.NewGuid().ToString().ToUpper(); + return Guid.NewGuid().ToString().ToUpperInvariant(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index cdac9acb25..7b1d5c228a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -19,6 +19,16 @@ namespace GodotTools.ProjectEditor public static class ProjectUtils { + public static void MSBuildLocatorRegisterDefaults(out Version version, out string path) + { + var instance = Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults(); + version = instance.Version; + path = instance.MSBuildPath; + } + + public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath) + => Microsoft.Build.Locator.MSBuildLocator.RegisterMSBuildPath(msbuildPath); + public static MSBuildProject Open(string path) { var root = ProjectRootElement.Open(path); @@ -42,7 +52,8 @@ namespace GodotTools.ProjectEditor var root = project.Root; string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue; - if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(root.Sdk) && + root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase)) return; root.Sdk = godotSdkAttrValue; diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets index aab2d73bdd..4baae77b34 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -8,8 +8,8 @@ </Target> <Target Name="GenerateGodotNupkgsVersionsFile" - DependsOnTargets="PrepareForBuild;_GenerateGodotNupkgsVersionsFile" - BeforeTargets="BeforeCompile;CoreCompile"> + DependsOnTargets="_GenerateGodotNupkgsVersionsFile" + BeforeTargets="PrepareForBuild;CompileDesignTime;BeforeCompile;CoreCompile"> <ItemGroup> <Compile Include="$(GeneratedGodotNupkgsVersionsFile)" /> <FileWrites Include="$(GeneratedGodotNupkgsVersionsFile)" /> diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj index 3bc1698c15..d60e6343ea 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj @@ -1,6 +1,8 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netstandard2.0</TargetFramework> + <TargetFramework>net6.0</TargetFramework> + <!-- Specify compile items manually to avoid including dangling generated items. --> + <EnableDefaultCompileItems>false</EnableDefaultCompileItems> </PropertyGroup> <Import Project="GenerateGodotNupkgsVersions.targets" /> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index d3107a69db..564775635d 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.ProjectEditor", "GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}" @@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Shared", "GodotTools.Shared\GodotTools.Shared.csproj", "{2758FFAF-8237-4CF2-B569-66BF8B3587BB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{D8C421B2-8911-41EB-B983-F675C7141EB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Internal", "..\..\glue\GodotSharp\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj", "{55666071-BEC1-4A52-8A98-9A4A7A947DBF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,5 +53,13 @@ Global {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.Build.0 = Release|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Release|Any CPU.Build.0 = Release|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index 28bf57dc21..3c5b897719 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs @@ -4,26 +4,36 @@ using Godot.Collections; using GodotTools.Internals; using Path = System.IO.Path; +#nullable enable + namespace GodotTools.Build { [Serializable] - public sealed class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization + public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization { - public string Solution { get; } - public string[] Targets { get; } - public string Configuration { get; } - public bool Restore { get; } + public string Solution { get; private set; } + public string Configuration { get; private set; } + public string? RuntimeIdentifier { get; private set; } + public string? PublishOutputDir { get; private set; } + public bool Restore { get; private set; } + public bool Rebuild { get; private set; } + public bool OnlyClean { get; private set; } + // TODO Use List once we have proper serialization - public Array<string> CustomProperties { get; } = new Array<string>(); + public Godot.Collections.Array CustomProperties { get; private set; } = new(); - public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); + public string LogsDirPath => + Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is BuildInfo other) - return other.Solution == Solution && other.Targets == Targets && - other.Configuration == Configuration && other.Restore == Restore && - other.CustomProperties == CustomProperties && other.LogsDirPath == LogsDirPath; + return other.Solution == Solution && + other.Configuration == Configuration && other.RuntimeIdentifier == RuntimeIdentifier && + other.PublishOutputDir == PublishOutputDir && other.Restore == Restore && + other.Rebuild == Rebuild && other.OnlyClean == OnlyClean && + other.CustomProperties == CustomProperties && + other.LogsDirPath == LogsDirPath; return false; } @@ -34,25 +44,44 @@ namespace GodotTools.Build { int hash = 17; hash = (hash * 29) + Solution.GetHashCode(); - hash = (hash * 29) + Targets.GetHashCode(); hash = (hash * 29) + Configuration.GetHashCode(); + hash = (hash * 29) + (RuntimeIdentifier?.GetHashCode() ?? 0); + hash = (hash * 29) + (PublishOutputDir?.GetHashCode() ?? 0); hash = (hash * 29) + Restore.GetHashCode(); + hash = (hash * 29) + Rebuild.GetHashCode(); + hash = (hash * 29) + OnlyClean.GetHashCode(); hash = (hash * 29) + CustomProperties.GetHashCode(); hash = (hash * 29) + LogsDirPath.GetHashCode(); return hash; } } + // Needed for instantiation from Godot, after reloading assemblies private BuildInfo() { + Solution = string.Empty; + Configuration = string.Empty; + } + + public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean) + { + Solution = solution; + Configuration = configuration; + Restore = restore; + Rebuild = rebuild; + OnlyClean = onlyClean; } - public BuildInfo(string solution, string[] targets, string configuration, bool restore) + public BuildInfo(string solution, string configuration, string runtimeIdentifier, + string publishOutputDir, bool restore, bool rebuild, bool onlyClean) { Solution = solution; - Targets = targets; Configuration = configuration; + RuntimeIdentifier = runtimeIdentifier; + PublishOutputDir = publishOutputDir; Restore = restore; + Rebuild = rebuild; + OnlyClean = onlyClean; } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 21bff70b15..43256953f5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -1,12 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; -using GodotTools.Ides.Rider; +using Godot; using GodotTools.Internals; -using JetBrains.Annotations; -using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; -using OS = GodotTools.Utils.OS; namespace GodotTools.Build { @@ -14,13 +12,8 @@ namespace GodotTools.Build { private static BuildInfo _buildInProgress; - public const string PropNameMSBuildMono = "MSBuild (Mono)"; - public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)"; - public const string PropNameMSBuildJetBrains = "MSBuild (JetBrains Rider)"; - public const string PropNameDotnetCli = "dotnet CLI"; - public const string MsBuildIssuesFileName = "msbuild_issues.csv"; - public const string MsBuildLogFileName = "msbuild_log.txt"; + private const string MsBuildLogFileName = "msbuild_log.txt"; public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason); @@ -62,11 +55,11 @@ namespace GodotTools.Build private static void PrintVerbose(string text) { - if (Godot.OS.IsStdoutVerbose()) - Godot.GD.Print(text); + if (OS.IsStdoutVerbose()) + GD.Print(text); } - public static bool Build(BuildInfo buildInfo) + private static bool Build(BuildInfo buildInfo) { if (_buildInProgress != null) throw new InvalidOperationException("A build is already in progress"); @@ -103,7 +96,8 @@ namespace GodotTools.Build } catch (Exception e) { - BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, + $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } @@ -148,7 +142,8 @@ namespace GodotTools.Build } catch (Exception e) { - BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, + $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } @@ -159,18 +154,54 @@ namespace GodotTools.Build } } - public static bool BuildProjectBlocking(string config, [CanBeNull] string[] targets = null, [CanBeNull] string platform = null) + private static bool Publish(BuildInfo buildInfo) { - var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets ?? new[] {"Build"}, config, restore: true); + if (_buildInProgress != null) + throw new InvalidOperationException("A build is already in progress"); - // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. - if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform)) - buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + _buildInProgress = buildInfo; - if (Internal.GodotIsRealTDouble()) - buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + try + { + BuildStarted?.Invoke(buildInfo); + + // Required in order to update the build tasks list + Internal.GodotMainIteration(); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = BuildSystem.Publish(buildInfo, StdOutputReceived, StdErrorReceived); + + if (exitCode != 0) + PrintVerbose( + $"dotnet publish exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - return BuildProjectBlocking(buildInfo); + BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error); + + return exitCode == 0; + } + catch (Exception e) + { + BuildLaunchFailed?.Invoke(buildInfo, + $"The publish method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + _buildInProgress = null; + } } private static bool BuildProjectBlocking(BuildInfo buildInfo) @@ -178,31 +209,109 @@ namespace GodotTools.Build if (!File.Exists(buildInfo.Solution)) return true; // No solution to build - // 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(buildInfo.Configuration == "ExportRelease" ? "Release" : "Debug"); + using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1); + + pr.Step("Building project solution", 0); - if (!string.IsNullOrEmpty(apiAssembliesUpdateError)) + if (!Build(buildInfo)) { - ShowBuildErrorDialog("Failed to update the Godot API assemblies"); + ShowBuildErrorDialog("Failed to build project solution"); return false; } - using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) + return true; + } + + private static bool CleanProjectBlocking(BuildInfo buildInfo) + { + if (!File.Exists(buildInfo.Solution)) + return true; // No solution to clean + + using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1); + + pr.Step("Cleaning project solution", 0); + + if (!Build(buildInfo)) { - pr.Step("Building project solution", 0); + ShowBuildErrorDialog("Failed to clean project solution"); + return false; + } - if (!Build(buildInfo)) - { - ShowBuildErrorDialog("Failed to build project solution"); - return false; - } + return true; + } + + private static bool PublishProjectBlocking(BuildInfo buildInfo) + { + using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1); + + pr.Step("Running dotnet publish", 0); + + if (!Publish(buildInfo)) + { + ShowBuildErrorDialog("Failed to publish .NET project"); + return false; } return true; } + private static BuildInfo CreateBuildInfo( + [DisallowNull] string configuration, + [AllowNull] string platform = null, + bool rebuild = false, + bool onlyClean = false + ) + { + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration, + restore: true, rebuild, onlyClean); + + // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. + if (platform != null || Utils.OS.PlatformNameMap.TryGetValue(OS.GetName(), out platform)) + buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + + if (Internal.GodotIsRealTDouble()) + buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + + return buildInfo; + } + + private static BuildInfo CreatePublishBuildInfo( + [DisallowNull] string configuration, + [DisallowNull] string platform, + [DisallowNull] string runtimeIdentifier, + [DisallowNull] string publishOutputDir + ) + { + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration, + runtimeIdentifier, publishOutputDir, restore: true, rebuild: false, onlyClean: false); + + buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + + if (Internal.GodotIsRealTDouble()) + buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + + return buildInfo; + } + + public static bool BuildProjectBlocking( + [DisallowNull] string configuration, + [AllowNull] string platform = null, + bool rebuild = false + ) => BuildProjectBlocking(CreateBuildInfo(configuration, platform, rebuild)); + + public static bool CleanProjectBlocking( + [DisallowNull] string configuration, + [AllowNull] string platform = null + ) => CleanProjectBlocking(CreateBuildInfo(configuration, platform, rebuild: false)); + + public static bool PublishProjectBlocking( + [DisallowNull] string configuration, + [DisallowNull] string platform, + [DisallowNull] string runtimeIdentifier, + string publishOutputDir + ) => PublishProjectBlocking(CreatePublishBuildInfo(configuration, + platform, runtimeIdentifier, publishOutputDir)); + public static bool EditorBuildCallback() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -215,7 +324,7 @@ namespace GodotTools.Build } catch (Exception e) { - Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) @@ -226,47 +335,6 @@ namespace GodotTools.Build public static void Initialize() { - // Build tool settings - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - - BuildTool msbuildDefault; - - if (OS.IsWindows) - { - if (RiderPathManager.IsExternalEditorSetToRider(editorSettings)) - msbuildDefault = BuildTool.JetBrainsMsBuild; - else - msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildVs; - } - else - { - msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildMono; - } - - EditorDef("mono/builds/build_tool", msbuildDefault); - - string hintString; - - if (OS.IsWindows) - { - hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," + - $"{PropNameMSBuildVs}:{(int)BuildTool.MsBuildVs}," + - $"{PropNameMSBuildJetBrains}:{(int)BuildTool.JetBrainsMsBuild}," + - $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}"; - } - else - { - hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," + - $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}"; - } - - editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary - { - ["type"] = Godot.Variant.Type.Int, - ["name"] = "mono/builds/build_tool", - ["hint"] = Godot.PropertyHint.Enum, - ["hint_string"] = hintString - }); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index ebdaca0ce8..96d1fc28bf 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -1,17 +1,16 @@ using Godot; using System; -using Godot.Collections; +using System.Diagnostics.CodeAnalysis; using GodotTools.Internals; -using JetBrains.Annotations; using File = GodotTools.Utils.File; using Path = System.IO.Path; namespace GodotTools.Build { - public class BuildOutputView : VBoxContainer, ISerializationListener + public partial class BuildOutputView : VBoxContainer, ISerializationListener { [Serializable] - private class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization + private partial class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization { public bool Warning { get; set; } public string File { get; set; } @@ -22,7 +21,8 @@ namespace GodotTools.Build public string ProjectFile { get; set; } } - [Signal] public event Action BuildStateChanged; + [Signal] + public delegate void BuildStateChangedEventHandler(); public bool HasBuildExited { get; private set; } = false; @@ -58,7 +58,7 @@ namespace GodotTools.Build } // TODO Use List once we have proper serialization. - private readonly Array<BuildIssue> _issues = new Array<BuildIssue>(); + private Godot.Collections.Array<BuildIssue> _issues = new(); private ItemList _issuesList; private PopupMenu _issuesListContextMenu; private TextEdit _buildLog; @@ -123,7 +123,7 @@ namespace GodotTools.Build throw new IndexOutOfRangeException("Item list index out of range"); // Get correct issue idx from issue list - int issueIndex = (int)(long)_issuesList.GetItemMetadata(idx); + int issueIndex = (int)_issuesList.GetItemMetadata(idx); if (issueIndex < 0 || issueIndex >= _issues.Count) throw new IndexOutOfRangeException("Issue index out of range"); @@ -133,7 +133,9 @@ namespace GodotTools.Build if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) return; - string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : _buildInfo.Solution.GetBaseDir(); + string projectDir = !string.IsNullOrEmpty(issue.ProjectFile) ? + issue.ProjectFile.GetBaseDir() : + _buildInfo.Solution.GetBaseDir(); string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); @@ -412,6 +414,16 @@ namespace GodotTools.Build { // In case it didn't update yet. We don't want to have to serialize any pending output. UpdateBuildLogText(); + + // NOTE: + // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. + // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. + BuildManager.BuildLaunchFailed -= BuildLaunchFailed; + BuildManager.BuildStarted -= BuildStarted; + BuildManager.BuildFinished -= BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred + BuildManager.StdOutputReceived -= StdOutputReceived; + BuildManager.StdErrorReceived -= StdErrorReceived; } public void OnAfterDeserialize() diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 02e9d98647..655be0ab5e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -1,61 +1,93 @@ -using GodotTools.Core; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Text; using System.Threading.Tasks; using GodotTools.BuildLogger; -using GodotTools.Internals; using GodotTools.Utils; -using Directory = System.IO.Directory; namespace GodotTools.Build { public static class BuildSystem { - private static string MonoWindowsBinDir + private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, + Action<string> stdErrHandler) { - get - { - string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin"); + string dotnetPath = DotNetFinder.FindDotNetExe(); - if (!Directory.Exists(monoWinBinDir)) - throw new FileNotFoundException("Cannot find the Windows Mono install bin directory."); + if (dotnetPath == null) + throw new FileNotFoundException("Cannot find the dotnet executable."); - return monoWinBinDir; - } + var startInfo = new ProcessStartInfo(dotnetPath); + + BuildArguments(buildInfo, startInfo.ArgumentList); + + string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString(); + stdOutHandler?.Invoke(launchMessage); + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine(launchMessage); + + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + startInfo.CreateNoWindow = true; + + // Needed when running from Developer Command Prompt for VS + RemovePlatformVariable(startInfo.EnvironmentVariables); + + var process = new Process { StartInfo = startInfo }; + + if (stdOutHandler != null) + process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data); + if (stdErrHandler != null) + process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data); + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + return process; } - private static Godot.EditorSettings EditorSettings => - GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + { + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) + { + process.WaitForExit(); + + return process.ExitCode; + } + } - private static bool UsingMonoMsBuildOnWindows + public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, + Action<string> stdErrHandler) { - get + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { - if (OS.IsWindows) - { - return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool") - == BuildTool.MsBuildMono; - } + await process.WaitForExitAsync(); - return false; + return process.ExitCode; } } - private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + private static Process LaunchPublish(BuildInfo buildInfo, Action<string> stdOutHandler, + Action<string> stdErrHandler) { - (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild(); + string dotnetPath = DotNetFinder.FindDotNetExe(); - if (msbuildPath == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); + if (dotnetPath == null) + throw new FileNotFoundException("Cannot find the dotnet executable."); - string compilerArgs = BuildArguments(buildTool, buildInfo); + var startInfo = new ProcessStartInfo(dotnetPath); - var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs); + BuildPublishArguments(buildInfo, startInfo.ArgumentList); - string launchMessage = $"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"; + string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString(); stdOutHandler?.Invoke(launchMessage); if (Godot.OS.IsStdoutVerbose()) Console.WriteLine(launchMessage); @@ -63,27 +95,16 @@ namespace GodotTools.Build startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - - if (UsingMonoMsBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } // Needed when running from Developer Command Prompt for VS RemovePlatformVariable(startInfo.EnvironmentVariables); - var process = new Process {StartInfo = startInfo}; + var process = new Process { StartInfo = startInfo }; if (stdOutHandler != null) - process.OutputDataReceived += (s, e) => stdOutHandler.Invoke(e.Data); + process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data); if (stdErrHandler != null) - process.ErrorDataReceived += (s, e) => stdErrHandler.Invoke(e.Data); + process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data); process.Start(); @@ -93,9 +114,9 @@ namespace GodotTools.Build return process; } - public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + public static int Publish(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) { - using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) + using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler)) { process.WaitForExit(); @@ -103,38 +124,101 @@ namespace GodotTools.Build } } - public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + private static void BuildArguments(BuildInfo buildInfo, Collection<string> arguments) { - using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) + // `dotnet clean` / `dotnet build` commands + arguments.Add(buildInfo.OnlyClean ? "clean" : "build"); + + // Solution + arguments.Add(buildInfo.Solution); + + // `dotnet clean` doesn't recognize these options + if (!buildInfo.OnlyClean) { - await process.WaitForExitAsync(); + // Restore + // `dotnet build` restores by default, unless requested not to + if (!buildInfo.Restore) + arguments.Add("--no-restore"); + + // Incremental or rebuild + if (buildInfo.Rebuild) + arguments.Add("--no-incremental"); + } - return process.ExitCode; + // Configuration + arguments.Add("-c"); + arguments.Add(buildInfo.Configuration); + + // Verbosity + arguments.Add("-v"); + arguments.Add("normal"); + + // Logger + AddLoggerArgument(buildInfo, arguments); + + // Custom properties + foreach (var customProperty in buildInfo.CustomProperties) + { + arguments.Add("-p:" + (string)customProperty); } } - private static string BuildArguments(BuildTool buildTool, BuildInfo buildInfo) + private static void BuildPublishArguments(BuildInfo buildInfo, Collection<string> arguments) { - string arguments = string.Empty; + arguments.Add("publish"); // `dotnet publish` command + + // Solution + arguments.Add(buildInfo.Solution); + + // Restore + // `dotnet publish` restores by default, unless requested not to + if (!buildInfo.Restore) + arguments.Add("--no-restore"); - if (buildTool == BuildTool.DotnetCli) - arguments += "msbuild"; // `dotnet msbuild` command + // Incremental or rebuild + if (buildInfo.Rebuild) + arguments.Add("--no-incremental"); - arguments += $@" ""{buildInfo.Solution}"""; + // Configuration + arguments.Add("-c"); + arguments.Add(buildInfo.Configuration); - if (buildInfo.Restore) - arguments += " /restore"; + // Runtime Identifier + arguments.Add("-r"); + arguments.Add(buildInfo.RuntimeIdentifier!); - arguments += $@" /t:{string.Join(",", buildInfo.Targets)} " + - $@"""/p:{"Configuration=" + buildInfo.Configuration}"" /v:normal " + - $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{buildInfo.LogsDirPath}"""; + // Self-published + arguments.Add("--self-contained"); + arguments.Add("true"); - foreach (string customProperty in buildInfo.CustomProperties) + // Verbosity + arguments.Add("-v"); + arguments.Add("normal"); + + // Logger + AddLoggerArgument(buildInfo, arguments); + + // Custom properties + foreach (var customProperty in buildInfo.CustomProperties) + { + arguments.Add("-p:" + (string)customProperty); + } + + // Publish output directory + if (buildInfo.PublishOutputDir != null) { - arguments += " /p:" + customProperty; + arguments.Add("-o"); + arguments.Add(buildInfo.PublishOutputDir); } + } + + private static void AddLoggerArgument(BuildInfo buildInfo, Collection<string> arguments) + { + string buildLoggerPath = Path.Combine(Internals.GodotSharpDirs.DataEditorToolsDir, + "GodotTools.BuildLogger.dll"); - return arguments; + arguments.Add( + $"-l:{typeof(GodotBuildLogger).FullName},{buildLoggerPath};{buildInfo.LogsDirPath}"); } private static void RemovePlatformVariable(StringDictionary environmentVariables) @@ -145,7 +229,7 @@ namespace GodotTools.Build foreach (string env in environmentVariables.Keys) { - if (env.ToUpper() == "PLATFORM") + if (env.ToUpperInvariant() == "PLATFORM") platformEnvironmentVariables.Add(env); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs deleted file mode 100644 index 837c8adddb..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GodotTools.Build -{ - public enum BuildTool : long - { - MsBuildMono, - MsBuildVs, - JetBrainsMsBuild, - DotnetCli - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs new file mode 100644 index 0000000000..7bce53308c --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using JetBrains.Annotations; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Build +{ + public static class DotNetFinder + { + [CanBeNull] + public static string FindDotNetExe() + { + // In the future, this method may do more than just search in PATH. We could look in + // known locations or use Godot's linked nethost to search from the hostfxr location. + + return OS.PathWhich("dotnet"); + } + + public static bool TryFindDotNetSdk( + Version expectedVersion, + [NotNullWhen(true)] out Version version, + [NotNullWhen(true)] out string path + ) + { + version = null; + path = null; + + string dotNetExe = FindDotNetExe(); + + if (string.IsNullOrEmpty(dotNetExe)) + return false; + + using Process process = new Process(); + process.StartInfo = new ProcessStartInfo(dotNetExe, "--list-sdks") + { + UseShellExecute = false, + RedirectStandardOutput = true + }; + + process.StartInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"] = "en-US"; + + var lines = new List<string>(); + + process.OutputDataReceived += (_, e) => + { + if (!string.IsNullOrWhiteSpace(e.Data)) + lines.Add(e.Data); + }; + + try + { + process.Start(); + } + catch + { + return false; + } + + process.BeginOutputReadLine(); + process.WaitForExit(); + + Version latestVersionMatch = null; + string matchPath = null; + + foreach (var line in lines) + { + string[] sdkLineParts = line.Trim() + .Split(' ', 2, StringSplitOptions.TrimEntries); + + if (sdkLineParts.Length < 2) + continue; + + if (!Version.TryParse(sdkLineParts[0], out var lineVersion)) + continue; + + // We're looking for the exact same major version + if (lineVersion.Major != expectedVersion.Major) + continue; + + if (latestVersionMatch != null && lineVersion < latestVersionMatch) + continue; + + latestVersionMatch = lineVersion; + matchPath = sdkLineParts[1].TrimStart('[').TrimEnd(']'); + } + + if (latestVersionMatch == null) + return false; + + version = latestVersionMatch; + path = Path.Combine(matchPath!, version.ToString()); + + return true; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 3c020a2589..6dae0a3a0e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -1,13 +1,12 @@ using System; using Godot; using GodotTools.Internals; -using JetBrains.Annotations; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; namespace GodotTools.Build { - public class MSBuildPanel : VBoxContainer + public partial class MSBuildPanel : VBoxContainer { public BuildOutputView BuildOutputView { get; private set; } @@ -28,7 +27,6 @@ namespace GodotTools.Build BuildOutputView.UpdateIssuesList(); } - [UsedImplicitly] public void BuildSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -57,7 +55,6 @@ namespace GodotTools.Build Internal.ReloadAssemblies(softReload: false); } - [UsedImplicitly] private void RebuildSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -73,7 +70,7 @@ namespace GodotTools.Build GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Rebuild" })) + if (!BuildManager.BuildProjectBlocking("Debug", rebuild: true)) return; // Build failed // Notify running game for hot-reload @@ -86,13 +83,12 @@ namespace GodotTools.Build Internal.ReloadAssemblies(softReload: false); } - [UsedImplicitly] private void CleanSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; // No solution to build - BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Clean" }); + _ = BuildManager.CleanProjectBlocking("Debug"); } private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs deleted file mode 100644 index a859c6f717..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Godot; -using GodotTools.Ides.Rider; -using GodotTools.Internals; -using Directory = System.IO.Directory; -using Environment = System.Environment; -using File = System.IO.File; -using Path = System.IO.Path; -using OS = GodotTools.Utils.OS; - -namespace GodotTools.Build -{ - public static class MsBuildFinder - { - private static string _msbuildToolsPath = string.Empty; - private static string _msbuildUnixPath = string.Empty; - - public static (string, BuildTool) FindMsBuild() - { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool"); - - if (OS.IsWindows) - { - switch (buildTool) - { - case BuildTool.DotnetCli: - { - string dotnetCliPath = OS.PathWhich("dotnet"); - if (!string.IsNullOrEmpty(dotnetCliPath)) - return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Visual Studio."); - goto case BuildTool.MsBuildVs; - } - case BuildTool.MsBuildVs: - { - if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath)) - { - // Try to search it again if it wasn't found last time or if it was removed from its location - _msbuildToolsPath = FindMsBuildToolsPathOnWindows(); - - if (string.IsNullOrEmpty(_msbuildToolsPath)) - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'."); - } - - if (!_msbuildToolsPath.EndsWith("\\")) - _msbuildToolsPath += "\\"; - - return (Path.Combine(_msbuildToolsPath, "MSBuild.exe"), BuildTool.MsBuildVs); - } - case BuildTool.MsBuildMono: - { - string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); - - if (!File.Exists(msbuildPath)) - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}"); - - return (msbuildPath, BuildTool.MsBuildMono); - } - case BuildTool.JetBrainsMsBuild: - { - string editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName); - - if (!File.Exists(editorPath)) - throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}"); - - var riderDir = new FileInfo(editorPath).Directory?.Parent; - - string msbuildPath = Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe"); - - if (!File.Exists(msbuildPath)) - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildJetBrains}'. Tried with path: {msbuildPath}"); - - return (msbuildPath, BuildTool.JetBrainsMsBuild); - } - default: - throw new IndexOutOfRangeException("Invalid build tool in editor settings"); - } - } - - if (OS.IsUnixLike) - { - switch (buildTool) - { - case BuildTool.DotnetCli: - { - string dotnetCliPath = FindBuildEngineOnUnix("dotnet"); - if (!string.IsNullOrEmpty(dotnetCliPath)) - return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Mono."); - goto case BuildTool.MsBuildMono; - } - case BuildTool.MsBuildMono: - { - if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath)) - { - // Try to search it again if it wasn't found last time or if it was removed from its location - _msbuildUnixPath = FindBuildEngineOnUnix("msbuild"); - } - - if (string.IsNullOrEmpty(_msbuildUnixPath)) - throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'"); - - return (_msbuildUnixPath, BuildTool.MsBuildMono); - } - default: - throw new IndexOutOfRangeException("Invalid build tool in editor settings"); - } - } - - throw new PlatformNotSupportedException(); - } - - private static IEnumerable<string> MsBuildHintDirs - { - get - { - var result = new List<string>(); - - if (OS.IsMacOS) - { - result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); - result.Add("/opt/local/bin/"); - result.Add("/usr/local/var/homebrew/linked/mono/bin/"); - result.Add("/usr/local/bin/"); - result.Add("/usr/local/bin/dotnet/"); - result.Add("/usr/local/share/dotnet/"); - } - - result.Add("/opt/novell/mono/bin/"); - - return result; - } - } - - private static string FindBuildEngineOnUnix(string name) - { - string ret = OS.PathWhich(name); - - if (!string.IsNullOrEmpty(ret)) - return ret; - - string retFallback = OS.PathWhich($"{name}.exe"); - - if (!string.IsNullOrEmpty(retFallback)) - return retFallback; - - foreach (string hintDir in MsBuildHintDirs) - { - string hintPath = Path.Combine(hintDir, name); - - if (File.Exists(hintPath)) - return hintPath; - } - - return string.Empty; - } - - private static string FindMsBuildToolsPathOnWindows() - { - if (!OS.IsWindows) - throw new PlatformNotSupportedException(); - - // Try to find 15.0 with vswhere - - string[] envNames = Internal.GodotIs32Bits() ? - envNames = new[] { "ProgramFiles", "ProgramW6432" } : - envNames = new[] { "ProgramFiles(x86)", "ProgramFiles" }; - - string vsWherePath = null; - foreach (var envName in envNames) - { - vsWherePath = Environment.GetEnvironmentVariable(envName); - if (!string.IsNullOrEmpty(vsWherePath)) - { - vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; - if (File.Exists(vsWherePath)) - break; - } - - vsWherePath = null; - } - - var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; - - var outputArray = new Godot.Collections.Array<string>(); - int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, - output: (Godot.Collections.Array)outputArray); - - if (exitCode != 0) - return string.Empty; - - if (outputArray.Count == 0) - return string.Empty; - - var lines = outputArray[0].Split('\n'); - - foreach (string line in lines) - { - int sepIdx = line.IndexOf(':'); - - if (sepIdx <= 0) - continue; - - string key = line.Substring(0, sepIdx); // No need to trim - - if (key != "installationPath") - continue; - - string value = line.Substring(sepIdx + 1).StripEdges(); - - if (string.IsNullOrEmpty(value)) - throw new FormatException("installationPath value is empty"); - - if (!value.EndsWith("\\")) - value += "\\"; - - // Since VS2019, the directory is simply named "Current" - string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin"); - - if (Directory.Exists(msbuildDir)) - return msbuildDir; - - // Directory name "15.0" is used in VS 2017 - return Path.Combine(value, "MSBuild\\15.0\\Bin"); - } - - return string.Empty; - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs index 63b97e981e..fdb86c8f34 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; @@ -39,7 +40,8 @@ namespace GodotTools.Build // Since this can be considered pretty much a new NuGet.Config, add the default nuget.org source as well XmlElement nugetOrgSourceEntry = xmlDoc.CreateElement("add"); nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = "nuget.org"; - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = "https://api.nuget.org/v3/index.json"; + nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = + "https://api.nuget.org/v3/index.json"; nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("protocolVersion")).Value = "3"; rootNode.AppendChild(xmlDoc.CreateElement("packageSources")).AppendChild(nugetOrgSourceEntry); } @@ -181,8 +183,8 @@ namespace GodotTools.Build // - The sha512 of the nupkg is base64 encoded. // - We can get the nuspec from the nupkg which is a Zip file. - string packageIdLower = packageId.ToLower(); - string packageVersionLower = packageVersion.ToLower(); + string packageIdLower = packageId.ToLowerInvariant(); + string packageVersionLower = packageVersion.ToLowerInvariant(); string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); @@ -227,9 +229,11 @@ namespace GodotTools.Build var nuspecEntry = archive.GetEntry(packageId + ".nuspec"); if (nuspecEntry == null) - throw new InvalidOperationException($"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file."); + throw new InvalidOperationException( + $"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file."); - nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name.ToLower().SimplifyGodotPath())); + nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name + .ToLowerInvariant().SimplifyGodotPath())); // Extract the other package files diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index e9718cc82c..fc325fc25b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -75,8 +75,17 @@ namespace GodotTools.Export } else { - string bits = features.Contains("64") ? "64" : features.Contains("32") ? "32" : null; - CompileAssembliesForDesktop(exporter, platform, isDebug, bits, aotOpts, aotTempDir, outputDataDir, assembliesPrepared, bclDir); + string arch = ""; + if (features.Contains("x86_64")) { + arch = "x86_64"; + } else if (features.Contains("x86_32")) { + arch = "x86_32"; + } else if (features.Contains("arm64")) { + arch = "arm64"; + } else if (features.Contains("arm32")) { + arch = "arm32"; + } + CompileAssembliesForDesktop(exporter, platform, isDebug, arch, aotOpts, aotTempDir, outputDataDir, assembliesPrepared, bclDir); } } @@ -112,7 +121,7 @@ namespace GodotTools.Export } } - public static void CompileAssembliesForDesktop(ExportPlugin exporter, string platform, bool isDebug, string bits, AotOptions aotOpts, string aotTempDir, string outputDataDir, IDictionary<string, string> assemblies, string bclDir) + public static void CompileAssembliesForDesktop(ExportPlugin exporter, string platform, bool isDebug, string arch, AotOptions aotOpts, string aotTempDir, string outputDataDir, IDictionary<string, string> assemblies, string bclDir) { foreach (var assembly in assemblies) { @@ -126,9 +135,9 @@ namespace GodotTools.Export string outputFileName = assemblyName + ".dll" + outputFileExtension; string tempOutputFilePath = Path.Combine(aotTempDir, outputFileName); - var compilerArgs = GetAotCompilerArgs(platform, isDebug, bits, aotOpts, assemblyPath, tempOutputFilePath); + var compilerArgs = GetAotCompilerArgs(platform, isDebug, arch, aotOpts, assemblyPath, tempOutputFilePath); - string compilerDirPath = GetMonoCrossDesktopDirName(platform, bits); + string compilerDirPath = GetMonoCrossDesktopDirName(platform, arch); ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir); @@ -289,7 +298,7 @@ MONO_AOT_MODE_LAST = 1000, // Archive the AOT object files into a static library var arFilePathsForAllArchs = new List<string>(); - string projectAssemblyName = GodotSharpEditor.ProjectAssemblyName; + string projectAssemblyName = GodotSharpDirs.ProjectAssemblyName; foreach (var archPathsPair in objFilePathsForiOSArch) { @@ -432,9 +441,9 @@ MONO_AOT_MODE_LAST = 1000, var androidToolPrefixes = new Dictionary<string, string> { - ["armeabi-v7a"] = "arm-linux-androideabi-", - ["arm64-v8a"] = "aarch64-linux-android-", - ["x86"] = "i686-linux-android-", + ["arm32"] = "arm-linux-androideabi-", + ["arm64"] = "aarch64-linux-android-", + ["x86_32"] = "i686-linux-android-", ["x86_64"] = "x86_64-linux-android-" }; @@ -547,9 +556,9 @@ MONO_AOT_MODE_LAST = 1000, { var androidAbis = new[] { - "armeabi-v7a", - "arm64-v8a", - "x86", + "arm32", + "arm64", + "x86_32", "x86_64" }; @@ -560,9 +569,9 @@ MONO_AOT_MODE_LAST = 1000, { var abiArchs = new Dictionary<string, string> { - ["armeabi-v7a"] = "armv7", - ["arm64-v8a"] = "aarch64-v8a", - ["x86"] = "i686", + ["arm32"] = "armv7", + ["arm64"] = "aarch64-v8a", + ["x86_32"] = "i686", ["x86_64"] = "x86_64" }; @@ -571,31 +580,25 @@ MONO_AOT_MODE_LAST = 1000, return $"{arch}-linux-android"; } - private static string GetMonoCrossDesktopDirName(string platform, string bits) + private static string GetMonoCrossDesktopDirName(string platform, string arch) { switch (platform) { case OS.Platforms.Windows: case OS.Platforms.UWP: { - string arch = bits == "64" ? "x86_64" : "i686"; return $"windows-{arch}"; } case OS.Platforms.MacOS: { - Debug.Assert(bits == null || bits == "64"); - string arch = "x86_64"; return $"{platform}-{arch}"; } case OS.Platforms.LinuxBSD: - 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: diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index cca18a2a1f..e1b5530b93 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -4,11 +4,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using GodotTools.Build; using GodotTools.Core; using GodotTools.Internals; -using JetBrains.Annotations; using static GodotTools.Internals.Globals; using Directory = GodotTools.Utils.Directory; using File = GodotTools.Utils.File; @@ -17,61 +15,13 @@ using Path = System.IO.Path; namespace GodotTools.Export { - public class ExportPlugin : EditorExportPlugin + public partial class ExportPlugin : EditorExportPlugin { - [Flags] - private enum I18NCodesets : long - { - None = 0, - CJK = 1, - MidEast = 2, - Other = 4, - Rare = 8, - West = 16, - All = CJK | MidEast | Other | Rare | West - } - - private string _maybeLastExportError; - - private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir) - { - var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets"); - - if (codesets == I18NCodesets.None) - return; - - void AddI18NAssembly(string name) => assemblies.Add(name, Path.Combine(bclDir, $"{name}.dll")); - - AddI18NAssembly("I18N"); - - if ((codesets & I18NCodesets.CJK) != 0) - AddI18NAssembly("I18N.CJK"); - if ((codesets & I18NCodesets.MidEast) != 0) - AddI18NAssembly("I18N.MidEast"); - if ((codesets & I18NCodesets.Other) != 0) - AddI18NAssembly("I18N.Other"); - if ((codesets & I18NCodesets.Rare) != 0) - AddI18NAssembly("I18N.Rare"); - if ((codesets & I18NCodesets.West) != 0) - AddI18NAssembly("I18N.West"); - } - public void RegisterExportSettings() { // TODO: These would be better as export preset options, but that doesn't seem to be supported yet GlobalDef("mono/export/include_scripts_content", false); - GlobalDef("mono/export/export_assemblies_inside_pck", true); - - GlobalDef("mono/export/i18n_codesets", I18NCodesets.All); - - ProjectSettings.AddPropertyInfo(new Godot.Collections.Dictionary - { - ["type"] = Variant.Type.Int, - ["name"] = "mono/export/i18n_codesets", - ["hint"] = PropertyHint.Flags, - ["hint_string"] = "CJK,MidEast,Other,Rare,West" - }); GlobalDef("mono/export/aot/enabled", false); GlobalDef("mono/export/aot/full_aot", false); @@ -85,11 +35,7 @@ namespace GodotTools.Export GlobalDef("mono/export/aot/android_toolchain_path", ""); } - private void AddFile(string srcPath, string dstPath, bool remap = false) - { - // Add file to the PCK - AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); - } + private string _maybeLastExportError; // With this method we can override how a file is exported in the PCK public override void _ExportFile(string path, string type, string[] features) @@ -100,7 +46,9 @@ namespace GodotTools.Export return; if (Path.GetExtension(path) != Internal.CSharpLanguageExtension) - throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); + 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 @@ -152,159 +100,93 @@ namespace GodotTools.Export if (!DeterminePlatformFromFeatures(features, out string platform)) throw new NotSupportedException("Target platform not supported"); + if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS } + .Contains(platform)) + { + throw new NotImplementedException("Target platform not yet implemented"); + } + string outputDir = new FileInfo(path).Directory?.FullName ?? - throw new FileNotFoundException("Base directory not found"); + throw new FileNotFoundException("Output base directory not found"); string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; - if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform)) - throw new Exception("Failed to build project"); + // TODO: This works for now, as we only implemented support for x86 family desktop so far, but it needs to be fixed + string arch = features.Contains("64") ? "x86_64" : "x86"; - // Add dependency assemblies + string ridOS = DetermineRuntimeIdentifierOS(platform); + string ridArch = DetermineRuntimeIdentifierArch(arch); + string runtimeIdentifier = $"{ridOS}-{ridArch}"; - var assemblies = new Godot.Collections.Dictionary<string, string>(); + // Create temporary publish output directory - string projectDllName = GodotSharpEditor.ProjectAssemblyName; - string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); - string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); + string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet", + $"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}"); - assemblies[projectDllName] = projectDllSrcPath; + if (!Directory.Exists(publishOutputTempDir)) + Directory.CreateDirectory(publishOutputTempDir); - string bclDir = DeterminePlatformBclDir(platform); + // Execute dotnet publish - if (platform == OS.Platforms.Android) + if (!BuildManager.PublishProjectBlocking(buildConfig, platform, + runtimeIdentifier, publishOutputTempDir)) { - string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext"); - string monoAndroidAssemblyPath = Path.Combine(godotAndroidExtProfileDir, "Mono.Android.dll"); + throw new Exception("Failed to build project"); + } - if (!File.Exists(monoAndroidAssemblyPath)) - throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath); + string soExt = ridOS switch + { + OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll", + OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib", + _ => "so" + }; - assemblies["Mono.Android"] = monoAndroidAssemblyPath; - } - else if (platform == OS.Platforms.HTML5) + if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll")) + // NativeAOT shared library output + && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}"))) { - // Ideally these would be added automatically since they're referenced by the wasm BCL assemblies. - // However, at least in the case of 'WebAssembly.Net.Http' for some reason the BCL assemblies - // reference a different version even though the assembly is the same, for some weird reason. - - var wasmFrameworkAssemblies = new[] { "WebAssembly.Bindings", "WebAssembly.Net.WebSockets" }; - - foreach (string thisWasmFrameworkAssemblyName in wasmFrameworkAssemblies) - { - string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName + ".dll"); - if (!File.Exists(thisWasmFrameworkAssemblyPath)) - throw new FileNotFoundException($"Assembly not found: '{thisWasmFrameworkAssemblyName}'", thisWasmFrameworkAssemblyPath); - assemblies[thisWasmFrameworkAssemblyName] = thisWasmFrameworkAssemblyPath; - } - - // Assemblies that can have a different name in a newer version. Newer version must come first and it has priority. - (string newName, string oldName)[] wasmFrameworkAssembliesOneOf = new[] - { - ("System.Net.Http.WebAssemblyHttpHandler", "WebAssembly.Net.Http") - }; - - foreach (var thisWasmFrameworkAssemblyName in wasmFrameworkAssembliesOneOf) - { - string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.newName + ".dll"); - if (File.Exists(thisWasmFrameworkAssemblyPath)) - { - assemblies[thisWasmFrameworkAssemblyName.newName] = thisWasmFrameworkAssemblyPath; - } - else - { - thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.oldName + ".dll"); - if (!File.Exists(thisWasmFrameworkAssemblyPath)) - { - throw new FileNotFoundException("Expected one of the following assemblies but none were found: " + - $"'{thisWasmFrameworkAssemblyName.newName}' / '{thisWasmFrameworkAssemblyName.oldName}'", - thisWasmFrameworkAssemblyPath); - } - - assemblies[thisWasmFrameworkAssemblyName.oldName] = thisWasmFrameworkAssemblyPath; - } - } + throw new NotSupportedException( + "Publish succeeded but project assembly not found in the output directory"); } - var initialAssemblies = assemblies.Duplicate(); - internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies); - - AddI18NAssemblies(assemblies, bclDir); - - string outputDataDir = null; + // Copy all files from the dotnet publish output directory to + // a data directory next to the Godot output executable. - if (PlatformHasTemplateDir(platform)) - outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir); + string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject()); - string apiConfig = isDebug ? "Debug" : "Release"; - string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); + if (Directory.Exists(outputDataDir)) + Directory.Delete(outputDataDir, recursive: true); // Clean first - bool assembliesInsidePck = (bool)ProjectSettings.GetSetting("mono/export/export_assemblies_inside_pck") || outputDataDir == null; + Directory.CreateDirectory(outputDataDir); - if (!assembliesInsidePck) + foreach (string dir in Directory.GetDirectories(publishOutputTempDir, "*", SearchOption.AllDirectories)) { - string outputDataGameAssembliesDir = Path.Combine(outputDataDir, "Assemblies"); - if (!Directory.Exists(outputDataGameAssembliesDir)) - Directory.CreateDirectory(outputDataGameAssembliesDir); + Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(publishOutputTempDir.Length + 1))); } - foreach (var assembly in assemblies) + foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories)) { - void AddToAssembliesDir(string fileSrcPath) - { - if (assembliesInsidePck) - { - string fileDstPath = Path.Combine(resAssembliesDir, fileSrcPath.GetFile()); - AddFile(fileSrcPath, fileDstPath); - } - else - { - Debug.Assert(outputDataDir != null); - string fileDstPath = Path.Combine(outputDataDir, "Assemblies", fileSrcPath.GetFile()); - File.Copy(fileSrcPath, fileDstPath); - } - } - - string assemblySrcPath = assembly.Value; - - string assemblyPathWithoutExtension = Path.ChangeExtension(assemblySrcPath, null); - string pdbSrcPath = assemblyPathWithoutExtension + ".pdb"; - - AddToAssembliesDir(assemblySrcPath); - - if (File.Exists(pdbSrcPath)) - AddToAssembliesDir(pdbSrcPath); + File.Copy(file, Path.Combine(outputDataDir, file.Substring(publishOutputTempDir.Length + 1))); } + } - // AOT compilation - bool aotEnabled = platform == OS.Platforms.iOS || (bool)ProjectSettings.GetSetting("mono/export/aot/enabled"); + private string DetermineRuntimeIdentifierOS(string platform) + => OS.DotNetOSPlatformMap[platform]; - if (aotEnabled) + private string DetermineRuntimeIdentifierArch(string arch) + { + return arch switch { - 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") ?? Array.Empty<string>(), - ExtraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options") ?? Array.Empty<string>(), - ToolchainPath = aotToolchainPath - }; - - AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir, outputDataDir, assemblies); - } + "x86" => "x86", + "x86_32" => "x86", + "x64" => "x64", + "x86_64" => "x64", + "armeabi-v7a" => "arm", + "arm64-v8a" => "arm64", + "armv7" => "arm", + "arm64" => "arm64", + _ => throw new ArgumentOutOfRangeException(nameof(arch), arch, "Unexpected architecture") + }; } public override void _ExportEnd() @@ -316,8 +198,10 @@ namespace GodotTools.Export if (Directory.Exists(aotTempDir)) Directory.Delete(aotTempDir, recursive: true); - // TODO: Just a workaround until the export plugins can be made to abort with errors - if (!string.IsNullOrEmpty(_maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading + // TODO: The following is just a workaround until the export plugins can be made to abort with errors + + // We check for empty as well, because it's set to empty after hot-reloading + if (!string.IsNullOrEmpty(_maybeLastExportError)) { string lastExportError = _maybeLastExportError; _maybeLastExportError = null; @@ -326,69 +210,11 @@ namespace GodotTools.Export } } - [NotNull] - private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir) - { - string target = isDebug ? "release_debug" : "release"; - - // 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}"; - - string templateDirPath = Path.Combine(Internal.FullExportTemplatesDir, TemplateDirName()); - bool validTemplatePathFound = true; - - if (!Directory.Exists(templateDirPath)) - { - validTemplatePathFound = false; - - if (isDebug) - { - target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name - templateDirPath = Path.Combine(Internal.FullExportTemplatesDir, TemplateDirName()); - validTemplatePathFound = true; - - if (!Directory.Exists(templateDirPath)) - validTemplatePathFound = false; - } - } - - if (!validTemplatePathFound) - throw new FileNotFoundException("Data template directory not found", templateDirPath); - - string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject()); - - if (Directory.Exists(outputDataDir)) - Directory.Delete(outputDataDir, recursive: true); // Clean first - - Directory.CreateDirectory(outputDataDir); - - foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); - } - - foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) - { - File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); - } - - return outputDataDir; - } - - private static bool PlatformHasTemplateDir(string platform) - { - // macOS 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.MacOS, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform); - } - private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform) { foreach (var feature in features) { - if (OS.PlatformNameMap.TryGetValue(feature, out platform)) + if (OS.PlatformFeatureMap.TryGetValue(feature, out platform)) return true; } @@ -396,87 +222,11 @@ namespace GodotTools.Export return false; } - private static string GetBclProfileDir(string profile) - { - string templatesDir = Internal.FullExportTemplatesDir; - return Path.Combine(templatesDir, "bcl", profile); - } - - private static string DeterminePlatformBclDir(string platform) - { - string templatesDir = Internal.FullExportTemplatesDir; - string platformBclDir = Path.Combine(templatesDir, "bcl", platform); - - if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) - { - string profile = DeterminePlatformBclProfile(platform); - platformBclDir = Path.Combine(templatesDir, "bcl", profile); - - if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) - { - if (PlatformRequiresCustomBcl(platform)) - throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}"); - - platformBclDir = typeof(object).Assembly.Location.GetBaseDir(); // Use the one we're running on - } - } - - return platformBclDir; - } - - /// <summary> - /// Determines whether the BCL bundled with the Godot editor can be used for the target platform, - /// or if it requires a custom BCL that must be distributed with the export templates. - /// </summary> - private static bool PlatformRequiresCustomBcl(string 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. - // We use the names 'net_4_x_win' and 'net_4_x' to differentiate between the two. - - bool isWinOrUwp = new[] - { - OS.Platforms.Windows, - OS.Platforms.UWP - }.Contains(platform); - - return OS.IsWindows ? !isWinOrUwp : isWinOrUwp; - } - - private static string DeterminePlatformBclProfile(string platform) - { - switch (platform) - { - case OS.Platforms.Windows: - case OS.Platforms.UWP: - return "net_4_x_win"; - case OS.Platforms.MacOS: - case OS.Platforms.LinuxBSD: - case OS.Platforms.Server: - case OS.Platforms.Haiku: - return "net_4_x"; - case OS.Platforms.Android: - return "monodroid"; - case OS.Platforms.iOS: - return "monotouch"; - case OS.Platforms.HTML5: - return "wasm"; - default: - throw new NotSupportedException($"Platform not supported: {platform}"); - } - } - private static string DetermineDataDirNameForProject() { string appName = (string)ProjectSettings.GetSetting("application/config/name"); string appNameSafe = appName.ToSafeDirName(); return $"data_{appNameSafe}"; } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialAssemblies, - string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencyAssemblies); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index b39c3d1c0d..0aca60dad4 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -13,13 +13,14 @@ using GodotTools.Internals; using GodotTools.ProjectEditor; using JetBrains.Annotations; using static GodotTools.Internals.Globals; +using Environment = System.Environment; using File = GodotTools.Utils.File; using OS = GodotTools.Utils.OS; using Path = System.IO.Path; namespace GodotTools { - public class GodotSharpEditor : EditorPlugin, ISerializationListener + public partial class GodotSharpEditor : EditorPlugin, ISerializationListener { private EditorSettings _editorSettings; @@ -39,28 +40,27 @@ namespace GodotTools public bool SkipBuildBeforePlaying { get; set; } = false; - public static string ProjectAssemblyName + [UsedImplicitly] + private bool CreateProjectSolutionIfNeeded() { - get + if (!File.Exists(GodotSharpDirs.ProjectSlnPath) || !File.Exists(GodotSharpDirs.ProjectCsProjPath)) { - string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); - projectAssemblyName = projectAssemblyName.ToSafeDirName(); - if (string.IsNullOrEmpty(projectAssemblyName)) - projectAssemblyName = "UnnamedProject"; - return projectAssemblyName; + return CreateProjectSolution(); } + + return true; } private bool CreateProjectSolution() { - using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3)) + using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2)) { pr.Step("Generating C# project...".TTR()); string resourceDir = ProjectSettings.GlobalizePath("res://"); string path = resourceDir; - string name = ProjectAssemblyName; + string name = GodotSharpDirs.ProjectAssemblyName; string guid = CsProjOperations.GenerateGameProject(path, name); @@ -75,7 +75,7 @@ namespace GodotTools { Guid = guid, PathRelativeToSolution = name + ".csproj", - Configs = new List<string> {"Debug", "ExportDebug", "ExportRelease"} + Configs = new List<string> { "Debug", "ExportDebug", "ExportRelease" } }; solution.AddNewProject(name, projectInfo); @@ -90,24 +90,6 @@ namespace GodotTools return false; } - pr.Step("Updating Godot API assemblies...".TTR()); - - string debugApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Debug"); - - if (!string.IsNullOrEmpty(debugApiAssembliesError)) - { - ShowErrorDialog("Failed to update the Godot API assemblies: " + debugApiAssembliesError); - return false; - } - - string releaseApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Release"); - - if (!string.IsNullOrEmpty(releaseApiAssembliesError)) - { - ShowErrorDialog("Failed to update the Godot API assemblies: " + releaseApiAssembliesError); - return false; - } - pr.Step("Done".TTR()); // Here, after all calls to progress_task_step @@ -141,7 +123,8 @@ namespace GodotTools try { string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder); + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + fallbackFolder); NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); } catch (Exception e) @@ -167,13 +150,6 @@ namespace GodotTools Instance.MSBuildPanel.BuildSolution(); } - public override void _Ready() - { - base._Ready(); - - MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; - } - private enum MenuOptions { CreateSln, @@ -197,7 +173,7 @@ namespace GodotTools [UsedImplicitly] public Error OpenInExternalEditor(Script script, int line, int col) { - var editorId = (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor"); + var editorId = (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor"); switch (editorId) { @@ -219,13 +195,15 @@ namespace GodotTools try { if (Godot.OS.IsStdoutVerbose()) - Console.WriteLine($"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}"); + Console.WriteLine( + $"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}"); OS.RunProcess(command, args); } catch (Exception e) { - GD.PushError($"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'"); + GD.PushError( + $"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'"); } break; @@ -347,7 +325,8 @@ namespace GodotTools [UsedImplicitly] public bool OverridesExternalEditor() { - return (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; + return (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor") != + ExternalEditorId.None; } public override bool _Build() @@ -368,7 +347,7 @@ namespace GodotTools // NOTE: The order in which changes are made to the project is important // Migrate to MSBuild project Sdks style if using the old style - ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName); + ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, GodotSharpDirs.ProjectAssemblyName); ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject); @@ -400,18 +379,49 @@ namespace GodotTools throw new InvalidOperationException(); Instance = this; + var dotNetSdkSearchVersion = Environment.Version; + + // First we try to find the .NET Sdk ourselves to make sure we get the + // correct version first (`RegisterDefaults` always picks the latest). + if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string sdkPath)) + { + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}"); + + ProjectUtils.MSBuildLocatorRegisterMSBuildPath(sdkPath); + } + else + { + try + { + ProjectUtils.MSBuildLocatorRegisterDefaults(out sdkVersion, out sdkPath); + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}"); + } + catch (InvalidOperationException e) + { + if (Godot.OS.IsStdoutVerbose()) + GD.PrintErr(e.ToString()); + GD.PushError($".NET Sdk not found. The required version is '{dotNetSdkSearchVersion}'."); + } + } + var editorInterface = GetEditorInterface(); var editorBaseControl = editorInterface.GetBaseControl(); _editorSettings = editorInterface.GetEditorSettings(); + GodotSharpDirs.RegisterProjectSettings(); + _errorDialog = new AcceptDialog(); editorBaseControl.AddChild(_errorDialog); MSBuildPanel = new MSBuildPanel(); + MSBuildPanel.Ready += () => + MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; _bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); - AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); + AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" }); _menuPopup = new PopupMenu(); _menuPopup.Hide(); @@ -472,9 +482,9 @@ namespace GodotTools _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { - ["type"] = Variant.Type.Int, + ["type"] = (int)Variant.Type.Int, ["name"] = "mono/editor/external_editor", - ["hint"] = PropertyHint.Enum, + ["hint"] = (int)PropertyHint.Enum, ["hint_string"] = settingsHintStr }); @@ -487,7 +497,8 @@ namespace GodotTools try { // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath); + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + NuGetUtils.GodotFallbackFolderPath); } catch (Exception e) { @@ -503,20 +514,23 @@ namespace GodotTools protected override void Dispose(bool disposing) { - base.Dispose(disposing); - - if (_exportPluginWeak != null) + if (disposing) { - // We need to dispose our export plugin before the editor destroys EditorSettings. - // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid - // will be freed after EditorSettings already was, and its device polling thread - // will try to access the EditorSettings singleton, resulting in null dereferencing. - (_exportPluginWeak.GetRef() as ExportPlugin)?.Dispose(); + if (IsInstanceValid(_exportPluginWeak)) + { + // We need to dispose our export plugin before the editor destroys EditorSettings. + // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid + // will be freed after EditorSettings already was, and its device polling thread + // will try to access the EditorSettings singleton, resulting in null dereferencing. + (_exportPluginWeak.GetRef().AsGodotObject() as ExportPlugin)?.Dispose(); + + _exportPluginWeak.Dispose(); + } - _exportPluginWeak.Dispose(); + GodotIdeManager?.Dispose(); } - GodotIdeManager?.Dispose(); + base.Dispose(disposing); } public void OnBeforeSerialize() @@ -533,8 +547,10 @@ namespace GodotTools public static GodotSharpEditor Instance { get; private set; } [UsedImplicitly] - private GodotSharpEditor() + private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) { + Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); + return new GodotSharpEditor().NativeInstance; } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index f1d45463c5..30525ba04a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -1,14 +1,24 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid> - <TargetFramework>net472</TargetFramework> - <LangVersion>7.2</LangVersion> + <TargetFramework>net6.0</TargetFramework> + <EnableDynamicLoading>true</EnableDynamicLoading> + <LangVersion>10</LangVersion> <!-- The Godot editor uses the Debug Godot API assemblies --> <GodotApiConfiguration>Debug</GodotApiConfiguration> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir> <GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir> + <ProduceReferenceAssembly>false</ProduceReferenceAssembly> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> + <!-- Needed for our source generators to work despite this not being a Godot game project --> + <PropertyGroup> + <IsGodotToolsProject>true</IsGodotToolsProject> + </PropertyGroup> + <ItemGroup> + <CompilerVisibleProperty Include="IsGodotToolsProject" /> + </ItemGroup> <PropertyGroup Condition=" Exists('$(GodotApiAssembliesDir)/GodotSharp.dll') "> <!-- The project is part of the Godot source tree --> <!-- Use the Godot source tree output folder instead of '$(ProjectDir)/bin' --> @@ -20,6 +30,8 @@ <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> + <!-- For RiderPathLocator --> + <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <Reference Include="GodotSharp"> <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath> <Private>False</Private> @@ -30,6 +42,10 @@ </Reference> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <ProjectReference Include="..\..\..\glue\GodotSharp\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + </ItemGroup> + <ItemGroup> <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" /> <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" /> diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index dd05f28af0..260d13a714 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -1,10 +1,11 @@ using Godot; using GodotTools.Internals; +using JetBrains.Annotations; using static GodotTools.Internals.Globals; namespace GodotTools { - public class HotReloadAssemblyWatcher : Node + public partial class HotReloadAssemblyWatcher : Node { private Timer _watchTimer; @@ -25,6 +26,7 @@ namespace GodotTools Internal.ReloadAssemblies(softReload: false); } + [UsedImplicitly] public void RestartTimer() { _watchTimer.Stop(); @@ -38,7 +40,7 @@ namespace GodotTools _watchTimer = new Timer { OneShot = false, - WaitTime = (float)EditorDef("mono/assembly_watch_interval_sec", 0.5) + WaitTime = 0.5f }; _watchTimer.Timeout += TimerTimeout; AddChild(_watchTimer); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 23339fe50b..95b60aded1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -8,7 +8,7 @@ using GodotTools.Internals; namespace GodotTools.Ides { - public sealed class GodotIdeManager : Node, ISerializationListener + public sealed partial class GodotIdeManager : Node, ISerializationListener { private MessagingServer _messagingServer; @@ -76,7 +76,7 @@ namespace GodotTools.Ides public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000) { - var editorId = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface() + var editorId = (ExternalEditorId)(int)GodotSharpEditor.Instance.GetEditorInterface() .GetEditorSettings().GetSetting("mono/editor/external_editor"); string editorIdentity = GetExternalEditorIdentity(editorId); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs index 71055f0125..4caab035de 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Runtime.Versioning; using Godot; -using JetBrains.Annotations; using Microsoft.Win32; using Newtonsoft.Json; using Directory = System.IO.Directory; @@ -113,6 +114,7 @@ namespace GodotTools.Ides.Rider return installInfos.ToArray(); } + [SupportedOSPlatform("windows")] private static RiderInfo[] CollectRiderInfosWindows() { var installInfos = new List<RiderInfo>(); @@ -217,6 +219,7 @@ namespace GodotTools.Ides.Rider throw new Exception("Unknown OS."); } + [SupportedOSPlatform("windows")] private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths) { using (var key = Registry.CurrentUser.OpenSubKey(registryKey)) @@ -229,6 +232,7 @@ namespace GodotTools.Ides.Rider } } + [SupportedOSPlatform("windows")] private static void CollectPathsFromRegistry(List<string> installPaths, RegistryKey key) { if (key == null) return; @@ -324,7 +328,7 @@ namespace GodotTools.Ides.Rider { public string install_location; - [CanBeNull] + [return: MaybeNull] public static string GetInstallLocationFromJson(string json) { try @@ -378,7 +382,7 @@ namespace GodotTools.Ides.Rider public string version; public string versionSuffix; - [CanBeNull] + [return: MaybeNull] internal static ProductInfo GetProductInfo(string json) { try @@ -402,7 +406,7 @@ namespace GodotTools.Ides.Rider // ReSharper disable once InconsistentNaming public ActiveApplication active_application; - [CanBeNull] + [return: MaybeNull] public static string GetLatestBuildFromJson(string json) { try diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index 3440eb701c..60602a5847 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -22,7 +22,7 @@ namespace GodotTools.Ides.Rider public static void Initialize() { var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var editor = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor"); + var editor = (ExternalEditorId)(int)editorSettings.GetSetting("mono/editor/external_editor"); if (editor == ExternalEditorId.Rider) { if (!editorSettings.HasSetting(EditorPathSettingName)) @@ -30,9 +30,9 @@ namespace GodotTools.Ides.Rider Globals.EditorDef(EditorPathSettingName, "Optional"); editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { - ["type"] = Variant.Type.String, + ["type"] = (int)Variant.Type.String, ["name"] = EditorPathSettingName, - ["hint"] = PropertyHint.File, + ["hint"] = (int)PropertyHint.File, ["hint_string"] = "" }); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs index 70ba7c733a..8f39ad063e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using Godot; +using Godot.NativeInterop; namespace GodotTools.Internals { @@ -8,19 +9,12 @@ namespace GodotTools.Internals { public string Task { get; } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_Create(string task, string label, int amount, bool canCancel); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_Dispose(string task); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_Step(string task, string state, int step, bool forceRefresh); - public EditorProgress(string task, string label, int amount, bool canCancel = false) { Task = task; - internal_Create(task, label, amount, canCancel); + using godot_string taskIn = Marshaling.ConvertStringToNative(task); + using godot_string labelIn = Marshaling.ConvertStringToNative(label); + Internal.godot_icall_EditorProgress_Create(taskIn, labelIn, amount, canCancel); } ~EditorProgress() @@ -33,18 +27,23 @@ namespace GodotTools.Internals public void Dispose() { - internal_Dispose(Task); + using godot_string taskIn = Marshaling.ConvertStringToNative(Task); + Internal.godot_icall_EditorProgress_Dispose(taskIn); GC.SuppressFinalize(this); } public void Step(string state, int step = -1, bool forceRefresh = true) { - internal_Step(Task, state, step, forceRefresh); + using godot_string taskIn = Marshaling.ConvertStringToNative(Task); + using godot_string stateIn = Marshaling.ConvertStringToNative(state); + Internal.godot_icall_EditorProgress_Step(taskIn, stateIn, step, forceRefresh); } public bool TryStep(string state, int step = -1, bool forceRefresh = true) { - return internal_Step(Task, state, step, forceRefresh); + using godot_string taskIn = Marshaling.ConvertStringToNative(Task); + using godot_string stateIn = Marshaling.ConvertStringToNative(state); + return Internal.godot_icall_EditorProgress_Step(taskIn, stateIn, step, forceRefresh); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs index 5c5ced8c29..acb7cc3ab0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs @@ -1,3 +1,4 @@ +using Godot.NativeInterop; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -5,35 +6,41 @@ namespace GodotTools.Internals { public static class Globals { - public static float EditorScale => internal_EditorScale(); - - public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) => - internal_GlobalDef(setting, defaultValue, restartIfChanged); - - public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) => - internal_EditorDef(setting, defaultValue, restartIfChanged); - - public static object EditorShortcut(string setting) => - internal_EditorShortcut(setting); + public static float EditorScale => Internal.godot_icall_Globals_EditorScale(); + + public static unsafe object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue); + Internal.godot_icall_Globals_GlobalDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result); + using (result) + return Marshaling.ConvertVariantToManagedObject(result); + } + + public static unsafe object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue); + Internal.godot_icall_Globals_EditorDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result); + using (result) + return Marshaling.ConvertVariantToManagedObject(result); + } + + public static object EditorShortcut(string setting) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + Internal.godot_icall_Globals_EditorShortcut(settingIn, out godot_variant result); + using (result) + return Marshaling.ConvertVariantToManagedObject(result); + } [SuppressMessage("ReSharper", "InconsistentNaming")] - public static string TTR(this string text) => internal_TTR(text); - - // Internal Calls - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern float internal_EditorScale(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object internal_EditorShortcut(string setting); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_TTR(string text); + public static string TTR(this string text) + { + using godot_string textIn = Marshaling.ConvertStringToNative(text); + Internal.godot_icall_Globals_TTR(textIn, out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 5e70c399b2..14285cc0f1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -1,103 +1,125 @@ -using System.Runtime.CompilerServices; +using System.IO; +using Godot; +using Godot.NativeInterop; +using GodotTools.Core; +using static GodotTools.Internals.Globals; namespace GodotTools.Internals { public static class GodotSharpDirs { - public static string ResDataDir => internal_ResDataDir(); - public static string ResMetadataDir => internal_ResMetadataDir(); - public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir(); - public static string ResAssembliesDir => internal_ResAssembliesDir(); - public static string ResConfigDir => internal_ResConfigDir(); - public static string ResTempDir => internal_ResTempDir(); - public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir(); - public static string ResTempAssembliesDir => internal_ResTempAssembliesDir(); - - public static string MonoUserDir => internal_MonoUserDir(); - public static string MonoLogsDir => internal_MonoLogsDir(); - - #region Tools-only - public static string MonoSolutionsDir => internal_MonoSolutionsDir(); - public static string BuildLogsDirs => internal_BuildLogsDirs(); - - public static string ProjectSlnPath => internal_ProjectSlnPath(); - public static string ProjectCsProjPath => internal_ProjectCsProjPath(); - - public static string DataEditorToolsDir => internal_DataEditorToolsDir(); - public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir(); - #endregion - - public static string DataMonoEtcDir => internal_DataMonoEtcDir(); - public static string DataMonoLibDir => internal_DataMonoLibDir(); - - #region Windows-only - public static string DataMonoBinDir => internal_DataMonoBinDir(); - #endregion - - - #region Internal - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResDataDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResMetadataDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResAssembliesBaseDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResAssembliesDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResConfigDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResTempDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResTempAssembliesBaseDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResTempAssembliesDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoUserDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoLogsDir(); - - #region Tools-only - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoSolutionsDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_BuildLogsDirs(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ProjectSlnPath(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ProjectCsProjPath(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataEditorToolsDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataEditorPrebuiltApiDir(); - #endregion - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataMonoEtcDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataMonoLibDir(); - - #region Windows-only - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataMonoBinDir(); - #endregion - - #endregion + public static string ResMetadataDir + { + get + { + Internal.godot_icall_GodotSharpDirs_ResMetadataDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static string MonoUserDir + { + get + { + Internal.godot_icall_GodotSharpDirs_MonoUserDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static string BuildLogsDirs + { + get + { + Internal.godot_icall_GodotSharpDirs_BuildLogsDirs(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static string DataEditorToolsDir + { + get + { + Internal.godot_icall_GodotSharpDirs_DataEditorToolsDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static void RegisterProjectSettings() + { + GlobalDef("dotnet/project/assembly_name", ""); + GlobalDef("dotnet/project/solution_directory", ""); + GlobalDef("dotnet/project/c#_project_directory", ""); + } + + private static void DetermineProjectLocation() + { + static string DetermineProjectName() + { + string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); + projectAssemblyName = projectAssemblyName.ToSafeDirName(); + if (string.IsNullOrEmpty(projectAssemblyName)) + projectAssemblyName = "UnnamedProject"; + return projectAssemblyName; + } + + _projectAssemblyName = (string)ProjectSettings.GetSetting("dotnet/project/assembly_name"); + if (string.IsNullOrEmpty(_projectAssemblyName)) + { + _projectAssemblyName = DetermineProjectName(); + ProjectSettings.SetSetting("dotnet/project/assembly_name", _projectAssemblyName); + } + + string slnParentDir = (string)ProjectSettings.GetSetting("dotnet/project/solution_directory"); + if (string.IsNullOrEmpty(slnParentDir)) + slnParentDir = "res://"; + + string csprojParentDir = (string)ProjectSettings.GetSetting("dotnet/project/c#_project_directory"); + if (string.IsNullOrEmpty(csprojParentDir)) + csprojParentDir = "res://"; + + _projectSlnPath = Path.Combine(ProjectSettings.GlobalizePath(slnParentDir), + string.Concat(_projectAssemblyName, ".sln")); + + _projectCsProjPath = Path.Combine(ProjectSettings.GlobalizePath(csprojParentDir), + string.Concat(_projectAssemblyName, ".csproj")); + } + + private static string _projectAssemblyName; + private static string _projectSlnPath; + private static string _projectCsProjPath; + + public static string ProjectAssemblyName + { + get + { + if (_projectAssemblyName == null) + DetermineProjectLocation(); + return _projectAssemblyName; + } + } + + public static string ProjectSlnPath + { + get + { + if (_projectSlnPath == null) + DetermineProjectLocation(); + return _projectSlnPath; + } + } + + public static string ProjectCsProjPath + { + get + { + if (_projectCsProjPath == null) + DetermineProjectLocation(); + return _projectCsProjPath; + } + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 12c90178c9..e3fe1622d0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -1,114 +1,161 @@ +using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Godot; +using Godot.NativeInterop; +using Godot.SourceGenerators.Internal; using GodotTools.IdeMessaging.Requests; namespace GodotTools.Internals { - public static class Internal + [SuppressMessage("ReSharper", "InconsistentNaming")] + [GenerateUnmanagedCallbacks(typeof(InternalUnmanagedCallbacks))] + internal static partial class Internal { public const string CSharpLanguageType = "CSharpScript"; public const string CSharpLanguageExtension = ".cs"; - public static string UpdateApiAssembliesFromPrebuilt(string config) => - internal_UpdateApiAssembliesFromPrebuilt(config); + public static string FullExportTemplatesDir + { + get + { + godot_icall_Internal_FullExportTemplatesDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } - public static string FullExportTemplatesDir => - internal_FullExportTemplatesDir(); + public static string SimplifyGodotPath(this string path) => Godot.StringExtensions.SimplifyPath(path); - public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path); + public static bool IsMacOSAppBundleInstalled(string bundleId) + { + using godot_string bundleIdIn = Marshaling.ConvertStringToNative(bundleId); + return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn); + } - public static bool IsMacOSAppBundleInstalled(string bundleId) => internal_IsMacOSAppBundleInstalled(bundleId); + public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits(); - public static bool GodotIs32Bits() => internal_GodotIs32Bits(); + public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble(); - public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble(); + public static void GodotMainIteration() => godot_icall_Internal_GodotMainIteration(); - public static void GodotMainIteration() => internal_GodotMainIteration(); + public static bool IsAssembliesReloadingNeeded() => godot_icall_Internal_IsAssembliesReloadingNeeded(); - public static ulong GetCoreApiHash() => internal_GetCoreApiHash(); + public static void ReloadAssemblies(bool softReload) => godot_icall_Internal_ReloadAssemblies(softReload); - public static ulong GetEditorApiHash() => internal_GetEditorApiHash(); + public static void EditorDebuggerNodeReloadScripts() => godot_icall_Internal_EditorDebuggerNodeReloadScripts(); - public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded(); + public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => + godot_icall_Internal_ScriptEditorEdit(resource.NativeInstance, line, col, grabFocus); - public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload); + public static void EditorNodeShowScriptScreen() => godot_icall_Internal_EditorNodeShowScriptScreen(); - public static void EditorDebuggerNodeReloadScripts() => internal_EditorDebuggerNodeReloadScripts(); + public static void EditorRunPlay() => godot_icall_Internal_EditorRunPlay(); - public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => - internal_ScriptEditorEdit(resource, line, col, grabFocus); + public static void EditorRunStop() => godot_icall_Internal_EditorRunStop(); - public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen(); + public static void ScriptEditorDebugger_ReloadScripts() => + godot_icall_Internal_ScriptEditorDebugger_ReloadScripts(); - public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, + string scriptFile) + { + using godot_string scriptFileIn = Marshaling.ConvertStringToNative(scriptFile); + godot_icall_Internal_CodeCompletionRequest((int)kind, scriptFileIn, out godot_packed_string_array res); + using (res) + return Marshaling.ConvertNativePackedStringArrayToSystemArray(res); + } - public static void EditorRunPlay() => internal_EditorRunPlay(); + #region Internal - public static void EditorRunStop() => internal_EditorRunStop(); + private static bool initialized = false; - public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts(); + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Global + internal static unsafe void Initialize(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + if (initialized) + throw new InvalidOperationException("Already initialized"); + initialized = true; - public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, string scriptFile) => - internal_CodeCompletionRequest((int)kind, scriptFile); + if (unmanagedCallbacksSize != sizeof(InternalUnmanagedCallbacks)) + throw new ArgumentException("Unmanaged callbacks size mismatch"); - #region Internal + _unmanagedCallbacks = Unsafe.AsRef<InternalUnmanagedCallbacks>((void*)unmanagedCallbacks); + } + + private partial struct InternalUnmanagedCallbacks + { + } + + /* + * IMPORTANT: + * The order of the methods defined in NativeFuncs must match the order + * in the array defined at the bottom of 'editor/editor_internal_calls.cpp'. + */ + + public static partial void godot_icall_GodotSharpDirs_ResMetadataDir(out godot_string r_dest); + + public static partial void godot_icall_GodotSharpDirs_MonoUserDir(out godot_string r_dest); + + public static partial void godot_icall_GodotSharpDirs_BuildLogsDirs(out godot_string r_dest); + + public static partial void godot_icall_GodotSharpDirs_DataEditorToolsDir(out godot_string r_dest); + + public static partial void godot_icall_EditorProgress_Create(in godot_string task, in godot_string label, + int amount, bool canCancel); + + public static partial void godot_icall_EditorProgress_Dispose(in godot_string task); + + public static partial bool godot_icall_EditorProgress_Step(in godot_string task, in godot_string state, + int step, + bool forceRefresh); + + private static partial void godot_icall_Internal_FullExportTemplatesDir(out godot_string dest); + + private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config); + private static partial bool godot_icall_Internal_GodotIs32Bits(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_FullExportTemplatesDir(); + private static partial bool godot_icall_Internal_GodotIsRealTDouble(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_SimplifyGodotPath(this string path); + private static partial void godot_icall_Internal_GodotMainIteration(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_IsMacOSAppBundleInstalled(string bundleId); + private static partial bool godot_icall_Internal_IsAssembliesReloadingNeeded(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_GodotIs32Bits(); + private static partial void godot_icall_Internal_ReloadAssemblies(bool softReload); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_GodotIsRealTDouble(); + private static partial void godot_icall_Internal_EditorDebuggerNodeReloadScripts(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_GodotMainIteration(); + private static partial bool godot_icall_Internal_ScriptEditorEdit(IntPtr resource, int line, int col, + bool grabFocus); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern ulong internal_GetCoreApiHash(); + private static partial void godot_icall_Internal_EditorNodeShowScriptScreen(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern ulong internal_GetEditorApiHash(); + private static partial void godot_icall_Internal_EditorRunPlay(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_IsAssembliesReloadingNeeded(); + private static partial void godot_icall_Internal_EditorRunStop(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_ReloadAssemblies(bool softReload); + private static partial void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorDebuggerNodeReloadScripts(); + private static partial void godot_icall_Internal_CodeCompletionRequest(int kind, in godot_string scriptFile, + out godot_packed_string_array res); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus); + public static partial float godot_icall_Globals_EditorScale(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorNodeShowScriptScreen(); + public static partial void godot_icall_Globals_GlobalDef(in godot_string setting, in godot_variant defaultValue, + bool restartIfChanged, out godot_variant result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoWindowsInstallRoot(); + public static partial void godot_icall_Globals_EditorDef(in godot_string setting, in godot_variant defaultValue, + bool restartIfChanged, out godot_variant result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorRunPlay(); + public static partial void + godot_icall_Globals_EditorShortcut(in godot_string setting, out godot_variant result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorRunStop(); + public static partial void godot_icall_Globals_TTR(in godot_string text, out godot_string dest); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_ScriptEditorDebugger_ReloadScripts(); + public static partial void godot_icall_Utils_OS_GetPlatformName(out godot_string dest); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string[] internal_CodeCompletionRequest(int kind, string scriptFile); + public static partial bool godot_icall_Utils_OS_UnixFileHasExecutableAccess(in godot_string filePath); #endregion } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs index 05499339b1..89bda704bb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs @@ -1,14 +1,14 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using Godot; using GodotTools.Core; -using JetBrains.Annotations; namespace GodotTools.Utils { public static class FsPathUtils { - private static readonly string _resourcePath = ProjectSettings.GlobalizePath("res://"); + private static readonly string ResourcePath = ProjectSettings.GlobalizePath("res://"); private static bool PathStartsWithAlreadyNorm(this string childPath, string parentPath) { @@ -30,11 +30,11 @@ namespace GodotTools.Utils return childPathNorm.PathStartsWithAlreadyNorm(parentPathNorm); } - [CanBeNull] + [return: MaybeNull] public static string LocalizePathWithCaseChecked(string path) { string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar; - string resourcePathNorm = _resourcePath.NormalizePath() + Path.DirectorySeparatorChar; + string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar; if (!pathNorm.PathStartsWithAlreadyNorm(resourcePathNorm)) return null; diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index 5cef6e5c3c..651922d019 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -1,24 +1,24 @@ +using Godot.NativeInterop; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; +using System.Runtime.Versioning; +using System.Text; +using GodotTools.Internals; namespace GodotTools.Utils { [SuppressMessage("ReSharper", "InconsistentNaming")] public static class OS { - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string GetPlatformName(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool UnixFileHasExecutableAccess(string filePath); - - public static class Names + /// <summary> + /// Display names for the OS platforms. + /// </summary> + private static class Names { public const string Windows = "Windows"; public const string MacOS = "macOS"; @@ -26,7 +26,6 @@ namespace GodotTools.Utils public const string FreeBSD = "FreeBSD"; public const string NetBSD = "NetBSD"; public const string BSD = "BSD"; - public const string Server = "Server"; public const string UWP = "UWP"; public const string Haiku = "Haiku"; public const string Android = "Android"; @@ -34,12 +33,14 @@ namespace GodotTools.Utils public const string HTML5 = "HTML5"; } + /// <summary> + /// Godot platform identifiers. + /// </summary> public static class Platforms { public const string Windows = "windows"; public const string MacOS = "macos"; public const string LinuxBSD = "linuxbsd"; - public const string Server = "server"; public const string UWP = "uwp"; public const string Haiku = "haiku"; public const string Android = "android"; @@ -47,6 +48,38 @@ namespace GodotTools.Utils public const string HTML5 = "javascript"; } + /// <summary> + /// OS name part of the .NET runtime identifier (RID). + /// See https://docs.microsoft.com/en-us/dotnet/core/rid-catalog. + /// </summary> + public static class DotNetOS + { + public const string Win = "win"; + public const string OSX = "osx"; + public const string Linux = "linux"; + public const string Win10 = "win10"; + public const string Android = "android"; + public const string iOS = "ios"; + public const string Browser = "browser"; + } + + public static readonly Dictionary<string, string> PlatformFeatureMap = new Dictionary<string, string>( + // Export `features` may be in lower case + StringComparer.InvariantCultureIgnoreCase + ) + { + ["Windows"] = Platforms.Windows, + ["macOS"] = Platforms.MacOS, + ["LinuxBSD"] = Platforms.LinuxBSD, + // "X11" for compatibility, temporarily, while we are on an outdated branch + ["X11"] = Platforms.LinuxBSD, + ["UWP"] = Platforms.UWP, + ["Haiku"] = Platforms.Haiku, + ["Android"] = Platforms.Android, + ["iOS"] = Platforms.iOS, + ["HTML5"] = Platforms.HTML5 + }; + public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string> { [Names.Windows] = Platforms.Windows, @@ -55,7 +88,6 @@ namespace GodotTools.Utils [Names.FreeBSD] = Platforms.LinuxBSD, [Names.NetBSD] = Platforms.LinuxBSD, [Names.BSD] = Platforms.LinuxBSD, - [Names.Server] = Platforms.Server, [Names.UWP] = Platforms.UWP, [Names.Haiku] = Platforms.Haiku, [Names.Android] = Platforms.Android, @@ -63,47 +95,78 @@ namespace GodotTools.Utils [Names.HTML5] = Platforms.HTML5 }; + public static readonly Dictionary<string, string> DotNetOSPlatformMap = new Dictionary<string, string> + { + [Platforms.Windows] = DotNetOS.Win, + [Platforms.MacOS] = DotNetOS.OSX, + // TODO: + // Does .NET 6 support BSD variants? If it does, it may need the name `unix` + // instead of `linux` in the runtime identifier. This would be a problem as + // Godot has a single export profile for both, named LinuxBSD. + [Platforms.LinuxBSD] = DotNetOS.Linux, + [Platforms.UWP] = DotNetOS.Win10, + [Platforms.Android] = DotNetOS.Android, + [Platforms.iOS] = DotNetOS.iOS, + [Platforms.HTML5] = DotNetOS.Browser + }; + private static bool IsOS(string name) { - return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest); + using (dest) + { + string platformName = Marshaling.ConvertStringToManaged(dest); + return name.Equals(platformName, StringComparison.OrdinalIgnoreCase); + } } private static bool IsAnyOS(IEnumerable<string> names) { - return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase)); + Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest); + using (dest) + { + string platformName = Marshaling.ConvertStringToManaged(dest); + return names.Any(p => p.Equals(platformName, StringComparison.OrdinalIgnoreCase)); + } } private static readonly IEnumerable<string> LinuxBSDPlatforms = new[] { Names.Linux, Names.FreeBSD, Names.NetBSD, Names.BSD }; private static readonly IEnumerable<string> UnixLikePlatforms = - new[] { Names.MacOS, Names.Server, Names.Haiku, Names.Android, Names.iOS } + new[] { Names.MacOS, Names.Haiku, Names.Android, Names.iOS } .Concat(LinuxBSDPlatforms).ToArray(); - private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows)); - private static readonly Lazy<bool> _isMacOS = new Lazy<bool>(() => IsOS(Names.MacOS)); - private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms)); - private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server)); - private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP)); - private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku)); - private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android)); - private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS)); - private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5)); - private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms)); - - public static bool IsWindows => _isWindows.Value || IsUWP; - public static bool IsMacOS => _isMacOS.Value; - public static bool IsLinuxBSD => _isLinuxBSD.Value; - public static bool IsServer => _isServer.Value; - public static bool IsUWP => _isUWP.Value; + private static readonly Lazy<bool> _isWindows = new(() => IsOS(Names.Windows)); + private static readonly Lazy<bool> _isMacOS = new(() => IsOS(Names.MacOS)); + private static readonly Lazy<bool> _isLinuxBSD = new(() => IsAnyOS(LinuxBSDPlatforms)); + private static readonly Lazy<bool> _isUWP = new(() => IsOS(Names.UWP)); + private static readonly Lazy<bool> _isHaiku = new(() => IsOS(Names.Haiku)); + private static readonly Lazy<bool> _isAndroid = new(() => IsOS(Names.Android)); + private static readonly Lazy<bool> _isiOS = new(() => IsOS(Names.iOS)); + private static readonly Lazy<bool> _isHTML5 = new(() => IsOS(Names.HTML5)); + private static readonly Lazy<bool> _isUnixLike = new(() => IsAnyOS(UnixLikePlatforms)); + + [SupportedOSPlatformGuard("windows")] public static bool IsWindows => _isWindows.Value || IsUWP; + + [SupportedOSPlatformGuard("osx")] public static bool IsMacOS => _isMacOS.Value; + + [SupportedOSPlatformGuard("linux")] public static bool IsLinuxBSD => _isLinuxBSD.Value; + + [SupportedOSPlatformGuard("windows")] 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; + + [SupportedOSPlatformGuard("android")] public static bool IsAndroid => _isAndroid.Value; + + [SupportedOSPlatformGuard("ios")] public static bool IsiOS => _isiOS.Value; + + [SupportedOSPlatformGuard("browser")] public static bool IsHTML5 => _isHTML5.Value; public static bool IsUnixLike => _isUnixLike.Value; public static char PathSep => IsWindows ? ';' : ':'; + [return: MaybeNull] public static string PathWhich([NotNull] string name) { if (IsWindows) @@ -112,9 +175,11 @@ namespace GodotTools.Utils return PathWhichUnix(name); } + [return: MaybeNull] private static string PathWhichWindows([NotNull] string name) { - string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>(); + string[] windowsExts = + Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>(); string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); char[] invalidPathChars = Path.GetInvalidPathChars(); @@ -133,7 +198,7 @@ namespace GodotTools.Utils string nameExt = Path.GetExtension(name); bool hasPathExt = !string.IsNullOrEmpty(nameExt) && - windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); + windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list @@ -141,12 +206,13 @@ namespace GodotTools.Utils return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists); return (from dir in searchDirs - select Path.Combine(dir, name) + select Path.Combine(dir, name) into path - from ext in windowsExts - select path + ext).FirstOrDefault(File.Exists); + from ext in windowsExts + select path + ext).FirstOrDefault(File.Exists); } + [return: MaybeNull] private static string PathWhichUnix([NotNull] string name) { string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); @@ -168,19 +234,16 @@ namespace GodotTools.Utils searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list return searchDirs.Select(dir => Path.Combine(dir, name)) - .FirstOrDefault(path => File.Exists(path) && UnixFileHasExecutableAccess(path)); + .FirstOrDefault(path => + { + using godot_string pathIn = Marshaling.ConvertStringToNative(path); + return File.Exists(path) && Internal.godot_icall_Utils_OS_UnixFileHasExecutableAccess(pathIn); + }); } public static void RunProcess(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)) + var startInfo = new ProcessStartInfo(command) { RedirectStandardOutput = true, RedirectStandardError = true, @@ -188,44 +251,104 @@ namespace GodotTools.Utils CreateNoWindow = true }; - using (Process process = Process.Start(startInfo)) - { - if (process == null) - throw new Exception("No process was started"); + foreach (string arg in arguments) + startInfo.ArgumentList.Add(arg); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - if (IsWindows && process.Id > 0) - User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself - } + using Process process = Process.Start(startInfo); + + if (process == null) + throw new Exception("No process was started"); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + if (IsWindows && process.Id > 0) + 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) + var startInfo = new ProcessStartInfo(command) { - // Not perfect, but as long as we are careful... - return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); - } + // Print the output + RedirectStandardOutput = false, + RedirectStandardError = false, + UseShellExecute = false + }; + + foreach (string arg in arguments) + startInfo.ArgumentList.Add(arg); - var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)); + Console.WriteLine(startInfo.GetCommandLineDisplay(new StringBuilder("Executing: ")).ToString()); - Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}"); + using var process = new Process { StartInfo = startInfo }; + process.Start(); + process.WaitForExit(); + + return process.ExitCode; + } - // Print the output - startInfo.RedirectStandardOutput = false; - startInfo.RedirectStandardError = false; + private static void AppendProcessFileNameForDisplay(this StringBuilder builder, string fileName) + { + if (builder.Length > 0) + builder.Append(' '); + + if (fileName.Contains(' ')) + { + builder.Append('"'); + builder.Append(fileName); + builder.Append('"'); + } + else + { + builder.Append(fileName); + } + } - startInfo.UseShellExecute = false; + private static void AppendProcessArgumentsForDisplay(this StringBuilder builder, + Collection<string> argumentList) + { + // This is intended just for reading. It doesn't need to be a valid command line. + // E.g.: We don't handle escaping of quotes. - using (var process = new Process { StartInfo = startInfo }) + foreach (string argument in argumentList) { - process.Start(); - process.WaitForExit(); + if (builder.Length > 0) + builder.Append(' '); - return process.ExitCode; + if (argument.Contains(' ')) + { + builder.Append('"'); + builder.Append(argument); + builder.Append('"'); + } + else + { + builder.Append(argument); + } } } + + public static StringBuilder GetCommandLineDisplay( + this ProcessStartInfo startInfo, + StringBuilder optionalBuilder = null + ) + { + var builder = optionalBuilder ?? new StringBuilder(); + + builder.AppendProcessFileNameForDisplay(startInfo.FileName); + + if (startInfo.ArgumentList.Count == 0) + { + builder.Append(' '); + builder.Append(startInfo.Arguments); + } + else + { + builder.AppendProcessArgumentsForDisplay(startInfo.ArgumentList); + } + + return builder; + } } } diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 2e628cb576..73d8f23081 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -41,69 +41,101 @@ #include "core/string/ucaps.h" #include "main/main.h" -#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_defs.h" -#include "../mono_gd/gd_mono_marshal.h" #include "../utils/path_utils.h" #include "../utils/string_utils.h" +StringBuilder &operator<<(StringBuilder &r_sb, const String &p_string) { + r_sb.append(p_string); + return r_sb; +} + +StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { + r_sb.append(p_cstring); + return r_sb; +} + #define CS_INDENT " " // 4 whitespaces #define INDENT1 CS_INDENT #define INDENT2 INDENT1 INDENT1 #define INDENT3 INDENT2 INDENT1 #define INDENT4 INDENT3 INDENT1 -#define INDENT5 INDENT4 INDENT1 -#define MEMBER_BEGIN "\n" INDENT2 +#define MEMBER_BEGIN "\n" INDENT1 #define OPEN_BLOCK "{\n" #define CLOSE_BLOCK "}\n" -#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK INDENT3 -#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK INDENT4 +#define OPEN_BLOCK_L1 INDENT1 OPEN_BLOCK +#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK +#define CLOSE_BLOCK_L1 INDENT1 CLOSE_BLOCK #define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK #define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK -#define CS_FIELD_MEMORYOWN "memoryOwn" +#define BINDINGS_GLOBAL_SCOPE_CLASS "GD" +#define BINDINGS_NATIVE_NAME_FIELD "NativeName" + +#define CS_PARAM_MEMORYOWN "memoryOwn" #define CS_PARAM_METHODBIND "method" #define CS_PARAM_INSTANCE "ptr" -#define CS_SMETHOD_GETINSTANCE "GetPtr" +#define CS_STATIC_METHOD_GETINSTANCE "GetPtr" #define CS_METHOD_CALL "Call" +#define CS_PROPERTY_SINGLETON "Singleton" +#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod" +#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod" + +#define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor" +#define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind" +#define CS_STATIC_FIELD_METHOD_NAME_PREFIX "MethodName_" +#define CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX "MethodProxyName_" +#define CS_STATIC_FIELD_SIGNAL_NAME_PREFIX "SignalName_" -#define GLUE_HEADER_FILE "glue_header.h" #define ICALL_PREFIX "godot_icall_" -#define SINGLETON_ICALL_SUFFIX "_get_singleton" -#define ICALL_GET_METHODBIND "__ClassDB_get_method" +#define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method" +#define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor" #define C_LOCAL_RET "ret" #define C_LOCAL_VARARG_RET "vararg_ret" #define C_LOCAL_PTRCALL_ARGS "call_args" -#define C_MACRO_OBJECT_CONSTRUCT "GODOTSHARP_INSTANCE_OBJECT" - -#define C_NS_MONOUTILS "GDMonoUtils" -#define C_NS_MONOINTERNALS "GDMonoInternals" -#define C_METHOD_TIE_MANAGED_TO_UNMANAGED C_NS_MONOINTERNALS "::tie_managed_to_unmanaged" -#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS "::unmanaged_get_managed" - -#define C_NS_MONOMARSHAL "GDMonoMarshal" -#define C_METHOD_MANAGED_TO_VARIANT C_NS_MONOMARSHAL "::mono_object_to_variant" -#define C_METHOD_MANAGED_FROM_VARIANT C_NS_MONOMARSHAL "::variant_to_mono_object" -#define C_METHOD_MONOSTR_TO_GODOT C_NS_MONOMARSHAL "::mono_string_to_godot" -#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(13) + +#define C_CLASS_NATIVE_FUNCS "NativeFuncs" +#define C_NS_MONOUTILS "InteropUtils" +#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS ".UnmanagedGetManaged" +#define C_METHOD_ENGINE_GET_SINGLETON C_NS_MONOUTILS ".EngineGetSingleton" + +#define C_NS_MONOMARSHAL "Marshaling" +#define C_METHOD_MONOSTR_TO_GODOT C_NS_MONOMARSHAL ".ConvertStringToNative" +#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL ".ConvertStringToManaged" +#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL ".ConvertSystemArrayToNative" #m_type +#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL ".ConvertNative" #m_type "ToSystemArray" +#define C_METHOD_MANAGED_TO_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToNative" +#define C_METHOD_MANAGED_FROM_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToManaged" +#define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToNative" +#define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToManaged" // Types that will be ignored by the generator and won't be available in C#. const Vector<String> ignored_types = { "PhysicsServer3DExtension" }; -const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); +void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) { + // C interface for enums is the same as that of 'uint32_t'. Remember to apply + // any of the changes done here to the 'uint32_t' type interface as well. + + r_enum_itype.cs_type = r_enum_itype.proxy_name; + r_enum_itype.cs_in_expr = "(int)%0"; + r_enum_itype.cs_out = "%5return (%2)%0(%1);"; + + { + // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. + r_enum_itype.c_in = "%5%0 %1_in = %1;\n"; + r_enum_itype.c_out = "%5return (%0)%1;\n"; + r_enum_itype.c_type = "long"; + r_enum_itype.c_arg_in = "&%s_in"; + } + r_enum_itype.c_type_in = "int"; + r_enum_itype.c_type_out = r_enum_itype.c_type_in; + r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name]; +} static String fix_doc_description(const String &p_bbcode) { // This seems to be the correct way to do this. It's the same EditorHelp does. @@ -359,23 +391,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=\"T:byte[]\"/>"); + xml_output.append("<see cref=\"byte\"/>[]"); } else if (tag == "PackedInt32Array") { - xml_output.append("<see cref=\"T:int[]\"/>"); + xml_output.append("<see cref=\"int\"/>[]"); } else if (tag == "PackedInt64Array") { - xml_output.append("<see cref=\"T:long[]\"/>"); + xml_output.append("<see cref=\"long\"/>[]"); } else if (tag == "PackedFloat32Array") { - xml_output.append("<see cref=\"T:float[]\"/>"); + xml_output.append("<see cref=\"float\"/>[]"); } else if (tag == "PackedFloat64Array") { - xml_output.append("<see cref=\"T:double[]\"/>"); + xml_output.append("<see cref=\"double\"/>[]"); } else if (tag == "PackedStringArray") { - xml_output.append("<see cref=\"T:string[]\"/>"); + xml_output.append("<see cref=\"string\"/>[]"); } else if (tag == "PackedVector2Array") { - xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector2[]\"/>"); + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>[]"); } else if (tag == "PackedVector3Array") { - xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector3[]\"/>"); + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>[]"); } else if (tag == "PackedColorArray") { - xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Color[]\"/>"); + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>[]"); } else { const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag)); @@ -794,49 +826,28 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI } } -void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { +Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_itype) { for (const MethodInterface &imethod : p_itype.methods) { if (imethod.is_virtual) { continue; } - const TypeInterface *return_type = _get_type_or_placeholder(imethod.return_type); + const TypeInterface *return_type = _get_type_or_null(imethod.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found - String im_sig = "IntPtr " CS_PARAM_METHODBIND; - String im_unique_sig = imethod.return_type.cname.operator String() + ",IntPtr"; + String im_unique_sig = get_ret_unique_sig(return_type) + ",CallMethodBind"; if (!imethod.is_static) { - im_sig += ", IntPtr " CS_PARAM_INSTANCE; - im_unique_sig += ",IntPtr"; + im_unique_sig += ",CallInstance"; } // Get arguments information - int i = 0; for (const ArgumentInterface &iarg : imethod.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); - - im_sig += ", "; - im_sig += arg_type->im_type_in; - im_sig += " arg"; - im_sig += itos(i + 1); + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found im_unique_sig += ","; - im_unique_sig += get_unique_sig(*arg_type); - - i++; - } - - String im_type_out = return_type->im_type_out; - - if (return_type->ret_as_byref_arg) { - // Doesn't affect the unique signature - im_type_out = "void"; - - im_sig += ", "; - im_sig += return_type->im_type_out; - im_sig += " argRet"; - - i++; + im_unique_sig += get_arg_unique_sig(*arg_type); } // godot_icall_{argc}_{icallcount} @@ -845,7 +856,15 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { icall_method += "_"; icall_method += itos(method_icalls.size()); - InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_type_out, im_sig, im_unique_sig); + InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_unique_sig); + + im_icall.is_vararg = imethod.is_vararg; + im_icall.is_static = imethod.is_static; + im_icall.return_type = imethod.return_type; + + for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) { + im_icall.argument_types.push_back(F->get().type); + } List<InternalCall>::Element *match = method_icalls.find(im_icall); @@ -859,47 +878,49 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { method_icalls_map.insert(&imethod, &added->get()); } } + + return OK; } void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) { + p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); p_output.append("using System;\n\n"); - p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); // The class where we put the extensions doesn't matter, so just use "GD". - p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); + p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{"); #define ARRAY_IS_EMPTY(m_type) \ - p_output.append("\n" INDENT2 "/// <summary>\n"); \ - p_output.append(INDENT2 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \ - p_output.append(INDENT2 "/// </summary>\n"); \ - p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \ - p_output.append(INDENT2 "/// <returns>Whether or not the array is empty.</returns>\n"); \ - p_output.append(INDENT2 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \ - p_output.append(INDENT2 OPEN_BLOCK); \ - p_output.append(INDENT3 "return instance == null || instance.Length == 0;\n"); \ - p_output.append(INDENT2 CLOSE_BLOCK); + p_output.append("\n" INDENT1 "/// <summary>\n"); \ + p_output.append(INDENT1 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \ + p_output.append(INDENT1 "/// </summary>\n"); \ + p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \ + p_output.append(INDENT1 "/// <returns>Whether or not the array is empty.</returns>\n"); \ + p_output.append(INDENT1 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \ + p_output.append(OPEN_BLOCK_L1); \ + p_output.append(INDENT2 "return instance == null || instance.Length == 0;\n"); \ + p_output.append(INDENT1 CLOSE_BLOCK); #define ARRAY_JOIN(m_type) \ - p_output.append("\n" INDENT2 "/// <summary>\n"); \ - p_output.append(INDENT2 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \ - p_output.append(INDENT2 "/// </summary>\n"); \ - p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ - p_output.append(INDENT2 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \ - p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \ - p_output.append(INDENT2 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \ - p_output.append(INDENT2 OPEN_BLOCK); \ - p_output.append(INDENT3 "return String.Join(delimiter, instance);\n"); \ - p_output.append(INDENT2 CLOSE_BLOCK); + p_output.append("\n" INDENT1 "/// <summary>\n"); \ + p_output.append(INDENT1 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \ + p_output.append(INDENT1 "/// </summary>\n"); \ + p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ + p_output.append(INDENT1 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \ + p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \ + p_output.append(INDENT1 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \ + p_output.append(OPEN_BLOCK_L1); \ + p_output.append(INDENT2 "return String.Join(delimiter, instance);\n"); \ + p_output.append(INDENT1 CLOSE_BLOCK); #define ARRAY_STRINGIFY(m_type) \ - p_output.append("\n" INDENT2 "/// <summary>\n"); \ - p_output.append(INDENT2 "/// Converts this " #m_type " array to a string with brackets.\n"); \ - p_output.append(INDENT2 "/// </summary>\n"); \ - p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ - p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \ - p_output.append(INDENT2 "public static string Stringify(this " #m_type "[] instance)\n"); \ - p_output.append(INDENT2 OPEN_BLOCK); \ - p_output.append(INDENT3 "return \"[\" + instance.Join() + \"]\";\n"); \ - p_output.append(INDENT2 CLOSE_BLOCK); + p_output.append("\n" INDENT1 "/// <summary>\n"); \ + p_output.append(INDENT1 "/// Converts this " #m_type " array to a string with brackets.\n"); \ + p_output.append(INDENT1 "/// </summary>\n"); \ + p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ + p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \ + p_output.append(INDENT1 "public static string Stringify(this " #m_type "[] instance)\n"); \ + p_output.append(OPEN_BLOCK_L1); \ + p_output.append(INDENT2 "return \"[\" + instance.Join() + \"]\";\n"); \ + p_output.append(INDENT1 CLOSE_BLOCK); #define ARRAY_ALL(m_type) \ ARRAY_IS_EMPTY(m_type) \ @@ -925,18 +946,18 @@ void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) { #undef ARRAY_JOIN #undef ARRAY_STRINGIFY - p_output.append(INDENT1 CLOSE_BLOCK); // End of GD class. - p_output.append(CLOSE_BLOCK); // End of namespace. + p_output.append(CLOSE_BLOCK); // End of GD class. } void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { // Constants (in partial GD class) + p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); + p_output.append("\n#pragma warning disable CS1591 // Disable warning: " "'Missing XML comment for publicly visible type or member'\n"); - p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); + p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{"); for (const ConstantInterface &iconstant : global_constants) { if (iconstant.const_doc && iconstant.const_doc->description.size()) { @@ -947,12 +968,12 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -967,7 +988,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append("\n"); } - p_output.append(INDENT1 CLOSE_BLOCK); // end of GD class + p_output.append(CLOSE_BLOCK); // end of GD class // Enums @@ -985,21 +1006,21 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { CRASH_COND(enum_class_name != "Variant"); // Hard-coded... - _log("Declaring global enum '%s' inside static class '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); + _log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); - p_output.append("\n" INDENT1 "public static partial class "); + p_output.append("\npublic partial struct "); p_output.append(enum_class_name); - p_output.append("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" OPEN_BLOCK); } if (ienum.is_flags) { - p_output.append("\n" INDENT1 "[System.Flags]"); + p_output.append("\n[System.Flags]"); } - p_output.append("\n" INDENT1 "public enum "); + p_output.append("\npublic enum "); p_output.append(enum_proxy_name); p_output.append(" : long"); - p_output.append("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" OPEN_BLOCK); const ConstantInterface &last = ienum.constants.back()->get(); for (const ConstantInterface &iconstant : ienum.constants) { @@ -1008,34 +1029,32 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - p_output.append(INDENT2 "/// <summary>\n"); + p_output.append(INDENT1 "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>\n"); + p_output.append(INDENT1 "/// </summary>\n"); } } - p_output.append(INDENT2); + p_output.append(INDENT1); p_output.append(iconstant.proxy_name); p_output.append(" = "); p_output.append(itos(iconstant.value)); p_output.append(&iconstant != &last ? ",\n" : "\n"); } - p_output.append(INDENT1 CLOSE_BLOCK); + p_output.append(CLOSE_BLOCK); if (enum_in_static_class) { - p_output.append(INDENT1 CLOSE_BLOCK); + p_output.append(CLOSE_BLOCK); } } - p_output.append(CLOSE_BLOCK); // end of namespace - p_output.append("\n#pragma warning restore CS1591\n"); } @@ -1106,42 +1125,38 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { compile_items.push_back(output_file); } - // Generate sources from compressed files + // Generate native calls StringBuilder cs_icalls_content; + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n"); cs_icalls_content.append("using System;\n" - "using System.Runtime.CompilerServices;\n" + "using System.Diagnostics.CodeAnalysis;\n" + "using System.Runtime.InteropServices;\n" + "using Godot.NativeInterop;\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 "{"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n"); + cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); + cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS "\n{"); cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = "); - cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); - cs_icalls_content.append(MEMBER_BEGIN "internal static uint bindings_version = "); - cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.append(MEMBER_BEGIN "internal static uint cs_glue_version = "); - cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_CORE)) + ";\n"); -#define ADD_INTERNAL_CALL(m_icall) \ - if (!m_icall.editor_only) { \ - cs_icalls_content.append(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.append(INDENT2 "internal 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"); \ - } + cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n"); - for (const InternalCall &internal_call : core_custom_icalls) { - ADD_INTERNAL_CALL(internal_call); - } - for (const InternalCall &internal_call : method_icalls) { - ADD_INTERNAL_CALL(internal_call); + for (const InternalCall &icall : method_icalls) { + if (icall.editor_only) { + continue; + } + Error err = _generate_cs_native_calls(icall, cs_icalls_content); + if (err != OK) { + return err; + } } -#undef ADD_INTERNAL_CALL - - cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + cs_icalls_content.append(CLOSE_BLOCK); String internal_methods_file = path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); @@ -1152,6 +1167,8 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { compile_items.push_back(internal_methods_file); + // Generate GeneratedIncludes.props + StringBuilder includes_props_content; includes_props_content.append("<Project>\n" " <ItemGroup>\n"); @@ -1215,41 +1232,40 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) { compile_items.push_back(output_file); } + // Generate native calls + StringBuilder cs_icalls_content; + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n"); cs_icalls_content.append("using System;\n" - "using System.Runtime.CompilerServices;\n" + "using System.Diagnostics.CodeAnalysis;\n" + "using System.Runtime.InteropServices;\n" + "using Godot.NativeInterop;\n" "\n"); - cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); - - cs_icalls_content.append(INDENT2 "internal static ulong godot_api_hash = "); - cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n"); - cs_icalls_content.append(INDENT2 "internal static uint bindings_version = "); - cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.append(INDENT2 "internal static uint cs_glue_version = "); - cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); - cs_icalls_content.append("\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n"); + cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); + cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" OPEN_BLOCK); -#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 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"); \ - } + cs_icalls_content.append(INDENT1 "internal static ulong godot_api_hash = "); + cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_EDITOR)) + ";\n"); - for (const InternalCall &internal_call : editor_custom_icalls) { - ADD_INTERNAL_CALL(internal_call); - } - for (const InternalCall &internal_call : method_icalls) { - ADD_INTERNAL_CALL(internal_call); - } + cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n"); -#undef ADD_INTERNAL_CALL + cs_icalls_content.append("\n"); - cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + for (const InternalCall &icall : method_icalls) { + if (!icall.editor_only) { + continue; + } + Error err = _generate_cs_native_calls(icall, cs_icalls_content); + if (err != OK) { + return err; + } + } + + cs_icalls_content.append(CLOSE_BLOCK); String internal_methods_file = path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); @@ -1260,6 +1276,8 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) { compile_items.push_back(internal_methods_file); + // Generate GeneratedIncludes.props + StringBuilder includes_props_content; includes_props_content.append("<Project>\n" " <ItemGroup>\n"); @@ -1343,16 +1361,15 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str CRASH_COND(itype.is_singleton); } - List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; - _log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data()); - String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types - StringBuilder output; + output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); + output.append("using System;\n"); // IntPtr output.append("using System.Diagnostics;\n"); // DebuggerBrowsable + output.append("using Godot.NativeInterop;\n"); output.append("\n" "#pragma warning disable CS1591 // Disable warning: " @@ -1360,7 +1377,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str "#pragma warning disable CS1573 // Disable warning: " "'Parameter has no matching param tag in the XML comment'\n"); - output.append("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + output.append("\n#nullable disable\n"); const DocData::ClassDoc *class_doc = itype.class_doc; @@ -1369,40 +1386,48 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - output.append(INDENT1 "/// <summary>\n"); + output.append("/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.append(INDENT1 "/// "); + output.append("/// "); output.append(summary_lines[i]); output.append("\n"); } - output.append(INDENT1 "/// </summary>\n"); + output.append("/// </summary>\n"); } } - output.append(INDENT1 "public "); + // We generate a `GodotClassName` attribute if the engine class name is not the same as the + // generated C# class name. This allows introspection code to find the name associated with + // the class. If the attribute is not present, the C# class name can be used instead. + if (itype.name != itype.proxy_name) { + output << "[GodotClassName(\"" << itype.name << "\")]\n"; + } + + output.append("public "); if (itype.is_singleton) { output.append("static partial class "); } else { - output.append(itype.is_instantiable ? "partial class " : "abstract partial class "); + // Even if the class is not instantiable, we can't declare it abstract because + // the engine can still instantiate them and return them via the scripting API. + // Example: `SceneTreeTimer` returned from `SceneTree.create_timer`. + // See the reverted commit: ef5672d3f94a7321ed779c922088bb72adbb1521 + output.append("partial class "); } output.append(itype.proxy_name); - if (itype.is_singleton) { - output.append("\n"); - } else if (is_derived_type) { + if (is_derived_type && !itype.is_singleton) { if (obj_types.has(itype.base_name)) { output.append(" : "); output.append(obj_types[itype.base_name].proxy_name); - output.append("\n"); } else { ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'."); return ERR_INVALID_DATA; } } - output.append(INDENT1 "{"); + output.append("\n{"); // Add constants @@ -1415,12 +1440,12 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.append(INDENT2 "/// "); + output.append(INDENT1 "/// "); output.append(summary_lines[i]); output.append("\n"); } - output.append(INDENT2 "/// </summary>"); + output.append(INDENT1 "/// </summary>"); } } @@ -1456,26 +1481,26 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - output.append(INDENT3 "/// <summary>\n"); + output.append(INDENT2 "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.append(INDENT3 "/// "); + output.append(INDENT2 "/// "); output.append(summary_lines[i]); output.append("\n"); } - output.append(INDENT3 "/// </summary>\n"); + output.append(INDENT2 "/// </summary>\n"); } } - output.append(INDENT3); + output.append(INDENT2); output.append(iconstant.proxy_name); output.append(" = "); output.append(itos(iconstant.value)); output.append(&iconstant != &last ? ",\n" : "\n"); } - output.append(INDENT2 CLOSE_BLOCK); + output.append(INDENT1 CLOSE_BLOCK); } // Add properties @@ -1491,55 +1516,68 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Add the type name and the singleton pointer as static fields output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n"); - output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 - "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 - "singleton = Engine.GetSingleton(typeof("); - output.append(itype.proxy_name); - output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); - output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); + output << MEMBER_BEGIN "public static Godot.Object " CS_PROPERTY_SINGLETON "\n" INDENT1 "{\n" + << INDENT2 "get\n" INDENT2 "{\n" INDENT3 "if (singleton == null)\n" + << INDENT4 "singleton = " C_METHOD_ENGINE_GET_SINGLETON "(typeof(" + << itype.proxy_name + << ").Name);\n" INDENT3 "return singleton;\n" INDENT2 "}\n" INDENT1 "}\n"; + + output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); output.append("\";\n"); + } else { + // IMPORTANT: We also generate the static fields for Godot.Object instead of declaring + // them manually in the `Object.base.cs` partial class declaration, because they're + // required by other static fields in this generated partial class declaration. + // Static fields are initialized in order of declaration, but when they're in different + // partial class declarations then it becomes harder to tell (Rider warns about this). - output.append(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); - output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.append("." ICALL_PREFIX); - output.append(itype.name); - output.append(SINGLETON_ICALL_SUFFIX "();\n"); - } else if (is_derived_type) { - // Add member fields + // Add native name static field - output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); + if (is_derived_type) { + output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n"; + } + + output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); output.append("\";\n"); - // Add default constructor if (itype.is_instantiable) { - output.append(MEMBER_BEGIN "public "); - output.append(itype.proxy_name); - output.append("() : this("); - output.append(itype.memory_own ? "true" : "false"); - - // The default constructor may also be called by the engine when instancing existing native objects - // The engine will initialize the pointer field of the managed side before calling the constructor - // This is why we only allocate a new native object from the constructor if the pointer field is not set - output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); - output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.append("." + ctor_method); - output.append("(this);\n" INDENT3 "_InitializeGodotScriptInstanceInternals();\n" CLOSE_BLOCK_L2); - } else { - // Hide the constructor + // Add native constructor static field + + output << MEMBER_BEGIN << "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly unsafe delegate* unmanaged<IntPtr> " + << CS_STATIC_FIELD_NATIVE_CTOR " = " ICALL_CLASSDB_GET_CONSTRUCTOR + << "(" BINDINGS_NATIVE_NAME_FIELD ");\n"; + } + + if (is_derived_type) { + // Add default constructor + if (itype.is_instantiable) { + output << MEMBER_BEGIN "public " << itype.proxy_name << "() : this(" + << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1 + << INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK + << INDENT3 "_ConstructAndInitialize(" CS_STATIC_FIELD_NATIVE_CTOR ", " + << BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: " + << (itype.is_ref_counted ? "true" : "false") << ");\n" + << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1; + } else { + // Hide the constructor + output.append(MEMBER_BEGIN "internal "); + output.append(itype.proxy_name); + output.append("() {}\n"); + } + + // Add.. em.. trick constructor. Sort of. output.append(MEMBER_BEGIN "internal "); output.append(itype.proxy_name); - output.append("() {}\n"); + output.append("(bool " CS_PARAM_MEMORYOWN ") : base(" CS_PARAM_MEMORYOWN ") {}\n"); } - - // Add.. em.. trick constructor. Sort of. - output.append(MEMBER_BEGIN "internal "); - output.append(itype.proxy_name); - output.append("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); } + // Methods + int method_bind_count = 0; for (const MethodInterface &imethod : itype.methods) { Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output); @@ -1547,30 +1585,153 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); } + // Signals + for (const SignalInterface &isignal : itype.signals_) { 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"); + // Script members look-up - if (!find_icall_by_name(singleton_icall.name, custom_icalls)) { - custom_icalls.push_back(singleton_icall); + if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) { + // Generate method names cache fields + + for (const MethodInterface &imethod : itype.methods) { + if (!imethod.is_virtual) { + continue; + } + + output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n" + << INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly StringName " + << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << " = \"" << imethod.name << "\";\n"; + + output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n" + << INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly StringName " + << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << " = \"" << imethod.proxy_name << "\";\n"; } - } - if (is_derived_type && itype.is_instantiable) { - InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); + // TODO: Only generate HasGodotClassMethod and InvokeGodotClassMethod if there's any method + + // Generate InvokeGodotClassMethod + + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") + << " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, " + << "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n" + << INDENT1 "{\n"; + + for (const MethodInterface &imethod : itype.methods) { + if (!imethod.is_virtual) { + continue; + } + + // We also call HasGodotClassMethod to ensure the method is overridden and avoid calling + // the stub implementation. This solution adds some extra overhead to calls, but it's + // much simpler than other solutions. This won't be a problem once we move to function + // pointers of generated wrappers for each method, as lookup will only happen once. + + // We check both native names (snake_case) and proxy names (PascalCase) + output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << " || method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << ") && argCount == " << itos(imethod.arguments.size()) + << " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)" + << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n" + << INDENT2 "{\n"; + + if (imethod.return_type.cname != name_cache.type_void) { + output << INDENT3 "var callRet = "; + } else { + output << INDENT3; + } + + output << imethod.proxy_name << "("; + + for (int i = 0; i < imethod.arguments.size(); i++) { + const ArgumentInterface &iarg = imethod.arguments[i]; + + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + + if (i != 0) { + output << ", "; + } + + if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) { + String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters); + + output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(i) + "]", arg_type->cs_type, arg_type->name) << ")"; + } else { + output << sformat(arg_type->cs_variant_to_managed, + "args[" + itos(i) + "]", arg_type->cs_type, arg_type->name); + } + } + + output << ");\n"; + + if (imethod.return_type.cname != name_cache.type_void) { + const TypeInterface *return_type = _get_type_or_null(imethod.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + + output << INDENT3 "ret = " + << sformat(return_type->cs_managed_to_variant, "callRet", return_type->cs_type, return_type->name) + << ";\n" + << INDENT3 "return true;\n"; + } else { + output << INDENT3 "ret = default;\n" + << INDENT3 "return true;\n"; + } + + output << INDENT2 "}\n"; + } + + if (is_derived_type) { + output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, argCount, out ret);\n"; + } else { + output << INDENT2 "ret = default;\n" + << INDENT2 "return false;\n"; + } + + output << INDENT1 "}\n"; + + // Generate HasGodotClassMethod + + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") + << " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n" + << INDENT1 "{\n"; + + for (const MethodInterface &imethod : itype.methods) { + if (!imethod.is_virtual) { + continue; + } - if (!find_icall_by_name(ctor_icall.name, custom_icalls)) { - custom_icalls.push_back(ctor_icall); + // We check for native names (snake_case). If we detect one, we call HasGodotClassMethod + // again, but this time with the respective proxy name (PascalCase). It's the job of + // user derived classes to override the method and check for those. Our C# source + // generators take care of generating those override methods. + output << INDENT2 "if (method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << ")\n" INDENT2 "{\n" + << INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_METHOD "(" + << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n" + << INDENT4 "return true;\n" + << INDENT3 "}\n" INDENT2 "}\n"; } + + if (is_derived_type) { + output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_METHOD "(method);\n"; + } else { + output << INDENT2 "return false;\n"; + } + + output << INDENT1 "}\n"; } - output.append(INDENT1 CLOSE_BLOCK /* class */ - CLOSE_BLOCK /* namespace */); + output.append(CLOSE_BLOCK /* class */); output.append("\n" "#pragma warning restore CS1591\n" @@ -1649,12 +1810,12 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -1669,15 +1830,15 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(prop_cs_type); p_output.append(" "); p_output.append(p_iprop.proxy_name); - p_output.append("\n" INDENT2 OPEN_BLOCK); + p_output.append("\n" OPEN_BLOCK_L1); if (getter) { - p_output.append(INDENT3 "get\n" + p_output.append(INDENT2 "get\n" // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning disable CS0618 // Disable warning about obsolete method\n" - OPEN_BLOCK_L3); + OPEN_BLOCK_L2 INDENT3); p_output.append("return "); p_output.append(getter->proxy_name + "("); @@ -1694,19 +1855,19 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte } p_output.append(");\n" - CLOSE_BLOCK_L3 + CLOSE_BLOCK_L2 // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning restore CS0618\n"); } if (setter) { - p_output.append(INDENT3 "set\n" + p_output.append(INDENT2 "set\n" // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning disable CS0618 // Disable warning about obsolete method\n" - OPEN_BLOCK_L3); + OPEN_BLOCK_L2 INDENT3); p_output.append(setter->proxy_name + "("); if (p_iprop.index != -1) { @@ -1722,19 +1883,20 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte } p_output.append("value);\n" - CLOSE_BLOCK_L3 + CLOSE_BLOCK_L2 // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning restore CS0618\n"); } - p_output.append(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); return OK; } Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) { - const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); + const TypeInterface *return_type = _get_type_or_null(p_imethod.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG, "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'."); @@ -1745,14 +1907,21 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf "' from the editor API. Core API cannot have dependencies on the editor API."); } - String method_bind_field = "__method_bind_" + itos(p_method_bind_count); + String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count); String arguments_sig; - String cs_in_statements; + StringBuilder cs_in_statements; + bool cs_in_expr_is_unsafe = false; String icall_params = method_bind_field; + if (!p_imethod.is_static) { - icall_params += ", " + sformat(p_itype.cs_in, "this"); + if (p_itype.cs_in.size()) { + cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, "this", + String(), String(), String(), INDENT2); + } + + icall_params += ", " + sformat(p_itype.cs_in_expr, "this"); } StringBuilder default_args_doc; @@ -1760,7 +1929,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Retrieve information from the arguments const ArgumentInterface &first = p_imethod.arguments.front()->get(); for (const ArgumentInterface &iarg : p_imethod.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'."); @@ -1813,27 +1983,23 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT) { // The default value of an argument must be constant. Otherwise we make it Nullable and do the following: // Type arg_in = arg.HasValue ? arg.Value : <non-const default value>; - String arg_in = iarg.name; - arg_in += "_in"; + String arg_or_defval_local = iarg.name; + arg_or_defval_local += "OrDefVal"; - cs_in_statements += arg_cs_type; - cs_in_statements += " "; - cs_in_statements += arg_in; - cs_in_statements += " = "; - cs_in_statements += iarg.name; + cs_in_statements << INDENT2 << arg_cs_type << " " << arg_or_defval_local << " = " << iarg.name; if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { - cs_in_statements += ".HasValue ? "; + cs_in_statements << ".HasValue ? "; } else { - cs_in_statements += " != null ? "; + cs_in_statements << " != null ? "; } - cs_in_statements += iarg.name; + cs_in_statements << iarg.name; if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { - cs_in_statements += ".Value : "; + cs_in_statements << ".Value : "; } else { - cs_in_statements += " : "; + cs_in_statements << " : "; } String cs_type = arg_cs_type; @@ -1843,10 +2009,18 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf String def_arg = sformat(iarg.default_argument, cs_type); - cs_in_statements += def_arg; - cs_in_statements += ";\n" INDENT3; + cs_in_statements << def_arg << ";\n"; + + if (arg_type->cs_in.size()) { + cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, arg_or_defval_local, + String(), String(), String(), INDENT2); + } - icall_params += arg_type->cs_in.is_empty() ? arg_in : sformat(arg_type->cs_in, arg_in); + if (arg_type->cs_in_expr.is_empty()) { + icall_params += arg_or_defval_local; + } else { + icall_params += sformat(arg_type->cs_in_expr, arg_or_defval_local, arg_type->c_type); + } // Apparently the name attribute must not include the @ String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name; @@ -1855,18 +2029,32 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf default_args_doc.append(MEMBER_BEGIN "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is <c>" + param_def_arg + "</c>.</param>"); } else { - icall_params += arg_type->cs_in.is_empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name); + if (arg_type->cs_in.size()) { + cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, iarg.name, + String(), String(), String(), INDENT2); + } + + icall_params += arg_type->cs_in_expr.is_empty() ? iarg.name : sformat(arg_type->cs_in_expr, iarg.name, arg_type->c_type); } + + cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe; } // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { - 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"); + p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly IntPtr " << method_bind_field << " = "; + + if (p_itype.is_singleton) { + // Singletons are static classes. They don't derive Godot.Object, + // so we need to specify the type to call the static method. + p_output << "Object."; + } + + p_output << ICALL_CLASSDB_GET_METHOD "(" BINDINGS_NATIVE_NAME_FIELD ", \"" + << p_imethod.name + << "\");\n"; } if (p_imethod.method_doc && p_imethod.method_doc->description.size()) { @@ -1877,12 +2065,12 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -1890,16 +2078,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(default_args_doc.as_string()); } - if (!p_imethod.is_internal) { - // TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be - // better to generate a table in the C++ glue instead. That way the strings wouldn't - // add that much extra bloat as they're already used in engine code. Also, it would - // probably be much faster than looking up the attributes when fetching methods. - p_output.append(MEMBER_BEGIN "[GodotMethod(\""); - p_output.append(p_imethod.name); - p_output.append("\")]"); - } - if (p_imethod.is_deprecated) { if (p_imethod.deprecation_message.is_empty()) { WARN_PRINT("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'."); @@ -1919,21 +2097,23 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("virtual "); } + if (cs_in_expr_is_unsafe) { + p_output.append("unsafe "); + } + String return_cs_type = return_type->cs_type + _get_generic_type_parameters(*return_type, p_imethod.return_type.generic_type_parameters); p_output.append(return_cs_type + " "); p_output.append(p_imethod.proxy_name + "("); - p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L2); + p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L1); if (p_imethod.is_virtual) { // Godot virtual method must be overridden, therefore we return a default value by default. if (return_type->cname == name_cache.type_void) { - p_output.append("return;\n" CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); } else { - p_output.append("return default("); - p_output.append(return_cs_type); - p_output.append(");\n" CLOSE_BLOCK_L2); + p_output.append(INDENT2 "return default;\n" CLOSE_BLOCK_L1); } return OK; // Won't increment method bind count @@ -1942,7 +2122,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (p_imethod.requires_object_call) { // Fallback to Godot's object.Call(string, params) - p_output.append(CS_METHOD_CALL "(\""); + p_output.append(INDENT2 CS_METHOD_CALL "(\""); p_output.append(p_imethod.name); p_output.append("\""); @@ -1951,7 +2131,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(iarg.name); } - p_output.append(");\n" CLOSE_BLOCK_L2); + p_output.append(");\n" CLOSE_BLOCK_L1); return OK; // Won't increment method bind count } @@ -1965,20 +2145,21 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf im_call += "."; im_call += im_icall->name; - if (p_imethod.arguments.size()) { - p_output.append(cs_in_statements); + if (p_imethod.arguments.size() && cs_in_statements.get_string_length() > 0) { + p_output.append(cs_in_statements.as_string()); } if (return_type->cname == name_cache.type_void) { - p_output.append(im_call + "(" + icall_params + ");\n"); + p_output << INDENT2 << im_call << "(" << icall_params << ");\n"; } else if (return_type->cs_out.is_empty()) { - p_output.append("return " + im_call + "(" + icall_params + ");\n"); + p_output << INDENT2 "return " << im_call << "(" << icall_params << ");\n"; } else { - p_output.append(sformat(return_type->cs_out, im_call, icall_params, return_cs_type, return_type->im_type_out)); + p_output.append(sformat(return_type->cs_out, im_call, icall_params, + return_cs_type, return_type->c_type_out, String(), INDENT2)); p_output.append("\n"); } - p_output.append(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); } p_method_bind_count++; @@ -1992,7 +2173,8 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // Retrieve information from the arguments const ArgumentInterface &first = p_isignal.arguments.front()->get(); for (const ArgumentInterface &iarg : p_isignal.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'."); @@ -2024,12 +2206,12 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -2058,7 +2240,9 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // 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(MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"); + p_output.append(INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN + "private static readonly StringName " CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); p_output.append(p_isignal.name); p_output.append(" = \""); p_output.append(p_isignal.name); @@ -2075,413 +2259,241 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(delegate_name); p_output.append(" "); p_output.append(p_isignal.proxy_name); - p_output.append("\n" OPEN_BLOCK_L2); + p_output.append("\n" OPEN_BLOCK_L1 INDENT2); if (p_itype.is_singleton) { - p_output.append("add => Singleton.Connect(__signal_name_"); + p_output.append("add => " CS_PROPERTY_SINGLETON ".Connect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } else { - p_output.append("add => Connect(__signal_name_"); + p_output.append("add => Connect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } 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_"); + p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } else { - p_output.append(INDENT3 "remove => Disconnect(__signal_name_"); + p_output.append(INDENT2 "remove => Disconnect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } p_output.append(p_isignal.name); p_output.append(", new Callable(value));\n"); - p_output.append(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); } return OK; } -Error BindingsGenerator::generate_glue(const String &p_output_dir) { - ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED); - - bool dir_exists = DirAccess::exists(p_output_dir); - ERR_FAIL_COND_V_MSG(!dir_exists, ERR_FILE_BAD_PATH, "The output directory does not exist."); - - StringBuilder output; - - output.append("/* THIS FILE IS GENERATED DO NOT EDIT */\n"); - output.append("#include \"" GLUE_HEADER_FILE "\"\n"); - output.append("\n#ifdef MONO_GLUE_ENABLED\n"); +Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output) { + bool ret_void = p_icall.return_type.cname == name_cache.type_void; - generated_icall_funcs.clear(); + const TypeInterface *return_type = _get_type_or_null(p_icall.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found - for (const KeyValue<StringName, TypeInterface> &type_elem : obj_types) { - const TypeInterface &itype = type_elem.value; + StringBuilder c_func_sig; + StringBuilder c_in_statements; + StringBuilder c_args_var_content; - bool is_derived_type = itype.base_name != StringName(); + c_func_sig << "IntPtr " CS_PARAM_METHODBIND; - if (!is_derived_type) { - // Some Object assertions - CRASH_COND(itype.cname != name_cache.type_Object); - CRASH_COND(!itype.is_instantiable); - CRASH_COND(itype.api_type != ClassDB::API_CORE); - CRASH_COND(itype.is_singleton); - } - - List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; - - OS::get_singleton()->print("Generating %s...\n", itype.name.utf8().get_data()); - - String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types - - for (const MethodInterface &imethod : itype.methods) { - Error method_err = _generate_glue_method(itype, imethod, output); - ERR_FAIL_COND_V_MSG(method_err != OK, method_err, - "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); - } - - if (itype.is_singleton) { - String singleton_icall_name = ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX; - InternalCall singleton_icall = InternalCall(itype.api_type, singleton_icall_name, "IntPtr"); - - if (!find_icall_by_name(singleton_icall.name, custom_icalls)) { - custom_icalls.push_back(singleton_icall); - } - - output.append("Object* "); - output.append(singleton_icall_name); - output.append("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\""); - output.append(itype.proxy_name); - output.append("\");\n" CLOSE_BLOCK "\n"); - } - - if (is_derived_type && itype.is_instantiable) { - InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); - - if (!find_icall_by_name(ctor_icall.name, custom_icalls)) { - custom_icalls.push_back(ctor_icall); - } - - output.append("Object* "); - output.append(ctor_method); - output.append("(MonoObject* obj) " OPEN_BLOCK - "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \""); - output.append(itype.name); - output.append("\");\n" - "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n" - "\treturn instance;\n" CLOSE_BLOCK "\n"); - } - } - - output.append("namespace GodotSharpBindings\n" OPEN_BLOCK "\n"); - - output.append("uint64_t get_core_api_hash() { return "); - output.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n"); - - output.append("#ifdef TOOLS_ENABLED\n" - "uint64_t get_editor_api_hash() { return "); - output.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n"); - output.append("#endif // TOOLS_ENABLED\n"); - - output.append("uint32_t get_bindings_version() { return "); - output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); - - output.append("uint32_t get_cs_glue_version() { return "); - output.append(String::num_uint64(CS_GLUE_VERSION) + "; }\n"); - - output.append("\nvoid register_generated_icalls() " OPEN_BLOCK); - output.append("\tgodot_register_glue_header_icalls();\n"); - -#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ - { \ - output.append("\tGDMonoUtils::add_internal_call("); \ - output.append("\"" BINDINGS_NAMESPACE "."); \ - output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ - output.append("::"); \ - output.append(m_icall.name); \ - output.append("\", "); \ - output.append(m_icall.name); \ - output.append(");\n"); \ - } - - bool tools_sequence = false; - for (const InternalCall &internal_call : core_custom_icalls) { - if (tools_sequence) { - if (!internal_call.editor_only) { - tools_sequence = false; - output.append("#endif\n"); - } - } else { - if (internal_call.editor_only) { - output.append("#ifdef TOOLS_ENABLED\n"); - tools_sequence = true; - } - } - - ADD_INTERNAL_CALL_REGISTRATION(internal_call); + if (!p_icall.is_static) { + c_func_sig += ", IntPtr " CS_PARAM_INSTANCE; } - if (tools_sequence) { - tools_sequence = false; - output.append("#endif\n"); - } - - output.append("#ifdef TOOLS_ENABLED\n"); - for (const InternalCall &internal_call : editor_custom_icalls) { - ADD_INTERNAL_CALL_REGISTRATION(internal_call); - } - output.append("#endif // TOOLS_ENABLED\n"); - - for (const InternalCall &internal_call : method_icalls) { - if (tools_sequence) { - if (!internal_call.editor_only) { - tools_sequence = false; - output.append("#endif\n"); - } - } else { - if (internal_call.editor_only) { - output.append("#ifdef TOOLS_ENABLED\n"); - tools_sequence = true; - } - } - - ADD_INTERNAL_CALL_REGISTRATION(internal_call); - } - - if (tools_sequence) { - tools_sequence = false; - output.append("#endif\n"); - } - -#undef ADD_INTERNAL_CALL_REGISTRATION - - output.append(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n"); - - output.append("\n#endif // MONO_GLUE_ENABLED\n"); - - Error save_err = _save_file(path::join(p_output_dir, "mono_glue.gen.cpp"), output); - if (save_err != OK) { - return save_err; - } - - OS::get_singleton()->print("Mono glue generated successfully\n"); - - return OK; -} - -uint32_t BindingsGenerator::get_version() { - return BINDINGS_GENERATOR_VERSION; -} - -Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) { - Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(file.is_null(), ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'."); - - file->store_string(p_content.as_string()); - - return OK; -} - -Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, StringBuilder &p_output) { - if (p_imethod.is_virtual) { - return OK; // Ignore - } - - bool ret_void = p_imethod.return_type.cname == name_cache.type_void; - - const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); - - String argc_str = itos(p_imethod.arguments.size()); - - String c_func_sig = "MethodBind* " CS_PARAM_METHODBIND; - if (!p_imethod.is_static) { - c_func_sig += ", " + p_itype.c_type_in + " " CS_PARAM_INSTANCE; - } - String c_in_statements; - String c_args_var_content; - // Get arguments information int i = 0; - for (const ArgumentInterface &iarg : p_imethod.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); + for (const TypeReference &arg_type_ref : p_icall.argument_types) { + const TypeInterface *arg_type = _get_type_or_null(arg_type_ref); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Return type not found String c_param_name = "arg" + itos(i + 1); - if (p_imethod.is_vararg) { - if (i < p_imethod.arguments.size() - 1) { - c_in_statements += sformat(arg_type->c_in.size() ? arg_type->c_in : TypeInterface::DEFAULT_VARARG_C_IN, "Variant", c_param_name); - c_in_statements += "\t" C_LOCAL_PTRCALL_ARGS ".set("; - c_in_statements += itos(i); - c_in_statements += sformat(", &%s_in);\n", c_param_name); + if (p_icall.is_vararg) { + if (i < p_icall.get_arguments_count() - 1) { + String c_in_vararg = arg_type->c_in_vararg; + + if (arg_type->is_object_type) { + c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromGodotObjectPtr(%1);\n"; + } + + ERR_FAIL_COND_V_MSG(c_in_vararg.is_empty(), ERR_BUG, + "VarArg support not implemented for parameter type: " + arg_type->name); + + c_in_statements + << sformat(c_in_vararg, return_type->c_type, c_param_name, + String(), String(), String(), INDENT3) + << INDENT3 C_LOCAL_PTRCALL_ARGS "[" << itos(i) + << "] = new IntPtr(&" << c_param_name << "_in);\n"; } } else { if (i > 0) { - c_args_var_content += ", "; + c_args_var_content << ", "; } if (arg_type->c_in.size()) { - c_in_statements += sformat(arg_type->c_in, arg_type->c_type, c_param_name); + c_in_statements << sformat(arg_type->c_in, arg_type->c_type, c_param_name, + String(), String(), String(), INDENT2); } - c_args_var_content += sformat(arg_type->c_arg_in, c_param_name); + c_args_var_content << sformat(arg_type->c_arg_in, c_param_name); } - c_func_sig += ", "; - c_func_sig += arg_type->c_type_in; - c_func_sig += " "; - c_func_sig += c_param_name; + c_func_sig << ", " << arg_type->c_type_in << " " << c_param_name; i++; } - if (return_type->ret_as_byref_arg) { - c_func_sig += ", "; - c_func_sig += return_type->c_type_in; - c_func_sig += " "; - c_func_sig += "arg_ret"; + String icall_method = p_icall.name; - i++; - } - - HashMap<const MethodInterface *, const InternalCall *>::ConstIterator match = method_icalls_map.find(&p_imethod); - ERR_FAIL_NULL_V(match, ERR_BUG); + // Generate icall function - const InternalCall *im_icall = match->value; - String icall_method = im_icall->name; + r_output << MEMBER_BEGIN "internal static unsafe " << (ret_void ? "void" : return_type->c_type_out) << " " + << icall_method << "(" << c_func_sig.as_string() << ") " OPEN_BLOCK; - if (!generated_icall_funcs.find(im_icall)) { - generated_icall_funcs.push_back(im_icall); + if (!ret_void && (!p_icall.is_vararg || return_type->cname != name_cache.type_Variant)) { + String ptrcall_return_type; + String initialization; - if (im_icall->editor_only) { - p_output.append("#ifdef TOOLS_ENABLED\n"); + if (return_type->is_object_type) { + ptrcall_return_type = return_type->is_ref_counted ? "godot_ref" : return_type->c_type; + initialization = " = default"; + } else { + ptrcall_return_type = return_type->c_type; } - // Generate icall function + r_output << INDENT2; - p_output.append((ret_void || return_type->ret_as_byref_arg) ? "void " : return_type->c_type_out + " "); - p_output.append(icall_method); - p_output.append("("); - p_output.append(c_func_sig); - p_output.append(") " OPEN_BLOCK); + if (return_type->is_ref_counted || return_type->c_type_is_disposable_struct) { + r_output << "using "; - if (!ret_void) { - String ptrcall_return_type; - String initialization; - - if (p_imethod.is_vararg && return_type->cname != name_cache.type_Variant) { - // VarArg methods always return Variant, but there are some cases in which MethodInfo provides - // a specific return type. We trust this information is valid. We need a temporary local to keep - // the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr, - // it could be deleted too early. This is the case with GDScript.new() which returns OBJECT. - // Alternatively, we could just return Variant, but that would result in a worse API. - p_output.append("\tVariant " C_LOCAL_VARARG_RET ";\n"); + if (initialization.is_empty()) { + initialization = " = default"; } + } else if (return_type->c_ret_needs_default_initialization) { + initialization = " = default"; + } - if (return_type->is_object_type) { - ptrcall_return_type = return_type->is_ref_counted ? "Ref<RefCounted>" : return_type->c_type; - initialization = return_type->is_ref_counted ? "" : " = nullptr"; - } else { - ptrcall_return_type = return_type->c_type; - } + r_output << ptrcall_return_type << " " C_LOCAL_RET << initialization << ";\n"; + } - p_output.append("\t" + ptrcall_return_type); - p_output.append(" " C_LOCAL_RET); - p_output.append(initialization + ";\n"); + if (!p_icall.is_static) { + r_output << INDENT2 "if (" CS_PARAM_INSTANCE " == IntPtr.Zero)\n" + << INDENT3 "throw new ArgumentNullException(nameof(" CS_PARAM_INSTANCE "));\n"; + } - String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "nullptr" : return_type->c_type_out + "()"; + String argc_str = itos(p_icall.get_arguments_count()); - if (!p_imethod.is_static) { - if (return_type->ret_as_byref_arg) { - p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = "); - p_output.append(fail_ret); - p_output.append("; ERR_FAIL_MSG(\"Parameter ' " CS_PARAM_INSTANCE " ' is null.\"); }\n"); - } else { - p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", "); - p_output.append(fail_ret); - p_output.append(");\n"); - } - } - } else { - if (!p_imethod.is_static) { - p_output.append("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); - } - } - - if (p_imethod.arguments.size()) { - if (p_imethod.is_vararg) { - String vararg_arg = "arg" + argc_str; - String real_argc_str = itos(p_imethod.arguments.size() - 1); // Arguments count without vararg - - p_output.append("\tint vararg_length = mono_array_length("); - p_output.append(vararg_arg); - p_output.append(");\n\tint total_length = "); - p_output.append(real_argc_str); - p_output.append(" + vararg_length;\n" - "\tArgumentsVector<Variant> varargs(vararg_length);\n" - "\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n"); - p_output.append(c_in_statements); - p_output.append("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK - "\t\tMonoObject* elem = mono_array_get("); - p_output.append(vararg_arg); - p_output.append(", MonoObject*, i);\n" - "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" - "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); - p_output.append(real_argc_str); - p_output.append(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK); - } else { - p_output.append(c_in_statements); - p_output.append("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); - p_output.append(argc_str + "] = { "); - p_output.append(c_args_var_content + " };\n"); - } - } + auto generate_call_and_return_stmts = [&](const char *base_indent) { + if (p_icall.is_vararg) { + // MethodBind Call + r_output << base_indent; - if (p_imethod.is_vararg) { - p_output.append("\tCallable::CallError vcall_error;\n\t"); + // VarArg methods always return Variant, but there are some cases in which MethodInfo provides + // a specific return type. We trust this information is valid. We need a temporary local to keep + // the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr, + // it could be deleted too early. This is the case with GDScript.new() which returns OBJECT. + // Alternatively, we could just return Variant, but that would result in a worse API. if (!ret_void) { - // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.append(C_LOCAL_VARARG_RET " = "); + r_output << "using godot_variant " << C_LOCAL_VARARG_RET " = "; } else { - p_output.append(C_LOCAL_RET " = "); + r_output << "using godot_variant " << C_LOCAL_RET " = "; } } - p_output.append(CS_PARAM_METHODBIND "->call("); - p_output.append(p_imethod.is_static ? "nullptr" : CS_PARAM_INSTANCE); - p_output.append(", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "nullptr"); - p_output.append(", total_length, vcall_error);\n"); + r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call(" + << CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE) + << ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null") + << ", total_length, out _);\n"; if (!ret_void) { - // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.append("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"); + if (return_type->cname == name_cache.enum_Error) { + r_output << base_indent << C_LOCAL_RET " = VariantUtils.ConvertToInt64(" C_LOCAL_VARARG_RET ");\n"; + } else { + // TODO: Use something similar to c_in_vararg (see usage above, with error if not implemented) + CRASH_NOW_MSG("Custom VarArg return type not implemented: " + return_type->name); + r_output << base_indent << C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"; + } } } } else { - p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall("); - p_output.append(p_imethod.is_static ? "nullptr" : CS_PARAM_INSTANCE); - p_output.append(", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "nullptr, "); - p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "nullptr);\n"); + // MethodBind PtrCall + r_output << base_indent << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_ptrcall(" + << CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE) + << ", " << (p_icall.get_arguments_count() ? C_LOCAL_PTRCALL_ARGS : "null") + << ", " << (!ret_void ? "&" C_LOCAL_RET ");\n" : "null);\n"); } + // Return statement + if (!ret_void) { if (return_type->c_out.is_empty()) { - p_output.append("\treturn " C_LOCAL_RET ";\n"); - } else if (return_type->ret_as_byref_arg) { - p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name, "arg_ret")); + r_output << base_indent << "return " C_LOCAL_RET ";\n"; } else { - p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); + r_output << sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, + return_type->name, String(), String(), base_indent); } } + }; + + if (p_icall.get_arguments_count()) { + if (p_icall.is_vararg) { + String vararg_arg = "arg" + argc_str; + String real_argc_str = itos(p_icall.get_arguments_count() - 1); // Arguments count without vararg + + p_icall.get_arguments_count(); + + r_output << INDENT2 "int vararg_length = " << vararg_arg << ".Length;\n" + << INDENT2 "int total_length = " << real_argc_str << " + vararg_length;\n"; + + r_output << INDENT2 "Span<godot_variant.movable> varargs_span = vararg_length <= VarArgsSpanThreshold ?\n" + << INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() :\n" + << INDENT3 "new godot_variant.movable[vararg_length];\n"; + + r_output << INDENT2 "Span<IntPtr> " C_LOCAL_PTRCALL_ARGS "_span = total_length <= VarArgsSpanThreshold ?\n" + << INDENT3 "stackalloc IntPtr[VarArgsSpanThreshold] :\n" + << INDENT3 "new IntPtr[total_length];\n"; + + r_output << INDENT2 "using var variantSpanDisposer = new VariantSpanDisposer(varargs_span);\n"; - p_output.append(CLOSE_BLOCK "\n"); + r_output << INDENT2 "fixed (godot_variant.movable* varargs = &MemoryMarshal.GetReference(varargs_span))\n" + << INDENT2 "fixed (IntPtr* " C_LOCAL_PTRCALL_ARGS " = " + "&MemoryMarshal.GetReference(" C_LOCAL_PTRCALL_ARGS "_span))\n" + << OPEN_BLOCK_L2; - if (im_icall->editor_only) { - p_output.append("#endif // TOOLS_ENABLED\n"); + r_output << c_in_statements.as_string(); + + r_output << INDENT3 "for (int i = 0; i < vararg_length; i++) " OPEN_BLOCK + << INDENT4 "varargs[i] = " << vararg_arg << "[i].NativeVar;\n" + << INDENT4 C_LOCAL_PTRCALL_ARGS "[" << real_argc_str << " + i] = new IntPtr(&varargs[i]);\n" + << CLOSE_BLOCK_L3; + + generate_call_and_return_stmts(INDENT3); + + r_output << CLOSE_BLOCK_L2; + } else { + r_output << c_in_statements.as_string(); + + r_output << INDENT2 "void** " C_LOCAL_PTRCALL_ARGS " = stackalloc void*[" + << argc_str << "] { " << c_args_var_content.as_string() << " };\n"; + + generate_call_and_return_stmts(INDENT2); } + } else { + generate_call_and_return_stmts(INDENT2); } + r_output << CLOSE_BLOCK_L1; + + return OK; +} + +Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) { + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(file.is_null(), ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'."); + + file->store_string(p_content.as_string()); + return OK; } @@ -2514,27 +2526,6 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con return nullptr; } -const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placeholder(const TypeReference &p_typeref) { - const TypeInterface *found = _get_type_or_null(p_typeref); - - if (found) { - return found; - } - - ERR_PRINT(String() + "Type not found. Creating placeholder: '" + p_typeref.cname.operator String() + "'."); - - HashMap<StringName, TypeInterface>::ConstIterator match = placeholder_types.find(p_typeref.cname); - - if (match) { - return &match->value; - } - - TypeInterface placeholder; - TypeInterface::create_placeholder_type(placeholder, p_typeref.cname); - - return &placeholder_types.insert(placeholder.cname, placeholder)->value; -} - const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) { if (p_generic_type_parameters.is_empty()) { return ""; @@ -2548,7 +2539,8 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface int i = 0; String params = "<"; for (const TypeReference ¶m_type : p_generic_type_parameters) { - const TypeInterface *param_itype = _get_type_or_placeholder(param_type); + const TypeInterface *param_itype = _get_type_or_null(param_type); + ERR_FAIL_NULL_V(param_itype, ""); ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "", "Generic type parameter is a singleton: '" + param_itype->name + "'."); @@ -2666,8 +2658,6 @@ bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant & case Variant::RECT2: case Variant::VECTOR3: case Variant::RID: - case Variant::ARRAY: - case Variant::DICTIONARY: case Variant::PACKED_BYTE_ARRAY: case Variant::PACKED_INT32_ARRAY: case Variant::PACKED_INT64_ARRAY: @@ -2680,6 +2670,10 @@ bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant & case Variant::CALLABLE: case Variant::SIGNAL: return p_arg_type.name == Variant::get_type_name(p_val.get_type()); + case Variant::ARRAY: + return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Array_generic; + case Variant::DICTIONARY: + return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Dictionary_generic; case Variant::OBJECT: return p_arg_type.is_object_type; case Variant::VECTOR2I: @@ -2744,18 +2738,27 @@ bool BindingsGenerator::_populate_object_type_interfaces() { itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted); itype.memory_own = itype.is_ref_counted; - itype.c_out = "\treturn "; + itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToGodotObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromGodotObject(%0)"; + + itype.c_out = "%5return "; itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED; - itype.c_out += itype.is_ref_counted ? "(%1.ptr());\n" : "(%1);\n"; + itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n"; - itype.cs_in = itype.is_singleton ? BINDINGS_PTR_FIELD : "Object." CS_SMETHOD_GETINSTANCE "(%0)"; + itype.cs_type = itype.proxy_name; - itype.c_type = "Object*"; + if (itype.is_singleton) { + itype.cs_in_expr = "Object." CS_STATIC_METHOD_GETINSTANCE "(" CS_PROPERTY_SINGLETON ")"; + } else { + itype.cs_in_expr = "Object." CS_STATIC_METHOD_GETINSTANCE "(%0)"; + } + + itype.cs_out = "%5return (%2)%0(%1);"; + + itype.c_arg_in = "(void*)%s"; + itype.c_type = "IntPtr"; itype.c_type_in = itype.c_type; - itype.c_type_out = "MonoObject*"; - itype.cs_type = itype.proxy_name; - itype.im_type_in = "IntPtr"; - itype.im_type_out = itype.proxy_name; + itype.c_type_out = "Object"; // Populate properties @@ -2846,6 +2849,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { if (method_info.flags & METHOD_FLAG_VIRTUAL) { imethod.is_virtual = true; + itype.has_virtual_methods = true; } PropertyInfo return_info = method_info.return_val; @@ -2887,7 +2891,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." + " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'."); } else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) { - imethod.return_type.cname = Variant::get_type_name(return_info.type); + imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string)); } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; @@ -2919,7 +2923,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.class_name != StringName()) { iarg.type.cname = arginfo.class_name; } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { - iarg.type.cname = Variant::get_type_name(arginfo.type); + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; @@ -2985,7 +2989,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false, "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'."); - // Classes starting with an underscore are ignored unless they're used as a property setter or getter + // Methods starting with an underscore are ignored unless they're used as a property setter or getter if (!imethod.is_virtual && imethod.name[0] == '_') { for (const PropertyInterface &iprop : itype.properties) { if (iprop.setter == imethod.name || iprop.getter == imethod.name) { @@ -3027,7 +3031,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.class_name != StringName()) { iarg.type.cname = arginfo.class_name; } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { - iarg.type.cname = Variant::get_type_name(arginfo.type); + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; @@ -3132,6 +3136,8 @@ bool BindingsGenerator::_populate_object_type_interfaces() { enum_itype.cname = StringName(enum_itype.name); enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name; TypeInterface::postsetup_enum_type(enum_itype); + enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; + enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); } @@ -3169,7 +3175,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar switch (p_val.get_type()) { case Variant::NIL: // Either Object type or Variant - r_iarg.default_argument = "null"; + r_iarg.default_argument = "default"; break; // Atomic types case Variant::BOOL: @@ -3189,8 +3195,13 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar case Variant::STRING_NAME: case Variant::NODE_PATH: 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; + if (r_iarg.default_argument.length() > 0) { + r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\""; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + } else { + // No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already. + r_iarg.default_argument = "null"; + } } else { CRASH_COND(r_iarg.type.cname != name_cache.type_String); r_iarg.default_argument = "\"" + r_iarg.default_argument + "\""; @@ -3236,8 +3247,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "null"; break; case Variant::DICTIONARY: - r_iarg.default_argument = "new %s()"; - r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + ERR_FAIL_COND_V_MSG(!p_val.operator Dictionary().is_empty(), false, + "Default value of type 'Dictionary' must be an empty dictionary."); + // The [cs_in] expression already interprets null values as empty dictionaries. + r_iarg.default_argument = "null"; + r_iarg.def_param_mode = ArgumentInterface::CONSTANT; break; case Variant::RID: ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false, @@ -3246,11 +3260,14 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false, "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value."); - r_iarg.default_argument = "null"; + r_iarg.default_argument = "default"; break; case Variant::ARRAY: - r_iarg.default_argument = "new %s { }"; - r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + ERR_FAIL_COND_V_MSG(!p_val.operator Array().is_empty(), false, + "Default value of type 'Array' must be an empty array."); + // The [cs_in] expression already interprets null values as empty arrays. + r_iarg.default_argument = "null"; + r_iarg.def_param_mode = ArgumentInterface::CONSTANT; break; case Variant::PACKED_BYTE_ARRAY: case Variant::PACKED_INT32_ARRAY: @@ -3325,12 +3342,12 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "default"; break; default: - CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type())); + ERR_FAIL_V_MSG(false, "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") { - r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "default") { + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } return true; @@ -3344,16 +3361,12 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { #define INSERT_STRUCT_TYPE(m_type) \ { \ itype = TypeInterface::create_value_type(String(#m_type)); \ - itype.c_in = "\t%0 %1_in = MARSHALLED_IN(" #m_type ", %1);\n"; \ - itype.c_out = "\t*%3 = MARSHALLED_OUT(" #m_type ", %1);\n"; \ - itype.c_arg_in = "&%s_in"; \ - itype.c_type_in = "GDMonoMarshal::M_" #m_type "*"; \ - itype.c_type_out = "GDMonoMarshal::M_" #m_type; \ - itype.cs_in = "ref %s"; \ - /* 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; \ + itype.c_type_in = #m_type "*"; \ + itype.c_type_out = itype.cs_type; \ + itype.cs_in_expr = "&%0"; \ + itype.cs_in_expr_is_unsafe = true; \ + itype.cs_variant_to_managed = "VariantUtils.ConvertTo%2(%0)"; \ + itype.cs_managed_to_variant = "VariantUtils.CreateFrom%2(%0)"; \ builtin_types.insert(itype.cname, itype); \ } @@ -3370,54 +3383,60 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { INSERT_STRUCT_TYPE(AABB) INSERT_STRUCT_TYPE(Color) INSERT_STRUCT_TYPE(Plane) + INSERT_STRUCT_TYPE(Vector4) + INSERT_STRUCT_TYPE(Vector4i) + INSERT_STRUCT_TYPE(Projection) #undef INSERT_STRUCT_TYPE // bool itype = TypeInterface::create_value_type(String("bool")); - { - // MonoBoolean <---> bool - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_type = "bool"; - itype.c_type_in = "MonoBoolean"; - itype.c_type_out = itype.c_type_in; - itype.c_arg_in = "&%s_in"; - } - itype.im_type_in = itype.name; - itype.im_type_out = itype.name; + itype.cs_in_expr = "%0.ToGodotBool()"; + itype.cs_out = "%5return %0(%1).ToBool();"; + itype.c_type = "godot_bool"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.c_type; + itype.c_arg_in = "&%s"; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromBool(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToBool(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromBool(%0)"; builtin_types.insert(itype.cname, itype); // Integer types { // C interface for 'uint32_t' is the same as that of enums. Remember to apply // any of the changes done here to 'TypeInterface::postsetup_enum_type' as well. -#define INSERT_INT_TYPE(m_name, m_c_type_in_out, m_c_type) \ - { \ - itype = TypeInterface::create_value_type(String(m_name)); \ - { \ - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; \ - itype.c_out = "\treturn (%0)%1;\n"; \ - itype.c_type = #m_c_type; \ - itype.c_arg_in = "&%s_in"; \ - } \ - itype.c_type_in = #m_c_type_in_out; \ - itype.c_type_out = itype.c_type_in; \ - itype.im_type_in = itype.name; \ - itype.im_type_out = itype.name; \ - builtin_types.insert(itype.cname, itype); \ +#define INSERT_INT_TYPE(m_name, m_int_struct_name) \ + { \ + itype = TypeInterface::create_value_type(String(m_name)); \ + if (itype.name != "long" && itype.name != "ulong") { \ + itype.c_in = "%5%0 %1_in = %1;\n"; \ + itype.c_out = "%5return (%0)%1;\n"; \ + itype.c_type = "long"; \ + itype.c_arg_in = "&%s_in"; \ + } else { \ + itype.c_arg_in = "&%s"; \ + } \ + itype.c_type_in = itype.name; \ + itype.c_type_out = itype.name; \ + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromInt(%1);\n"; \ + itype.cs_variant_to_managed = "VariantUtils.ConvertTo" m_int_struct_name "(%0)"; \ + itype.cs_managed_to_variant = "VariantUtils.CreateFromInt(%0)"; \ + builtin_types.insert(itype.cname, itype); \ } // The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type' - INSERT_INT_TYPE("sbyte", int8_t, int64_t); - INSERT_INT_TYPE("short", int16_t, int64_t); - INSERT_INT_TYPE("int", int32_t, int64_t); - INSERT_INT_TYPE("long", int64_t, int64_t); - INSERT_INT_TYPE("byte", uint8_t, int64_t); - INSERT_INT_TYPE("ushort", uint16_t, int64_t); - INSERT_INT_TYPE("uint", uint32_t, int64_t); - INSERT_INT_TYPE("ulong", uint64_t, int64_t); + INSERT_INT_TYPE("sbyte", "Int8"); + INSERT_INT_TYPE("short", "Int16"); + INSERT_INT_TYPE("int", "Int32"); + INSERT_INT_TYPE("long", "Int64"); + INSERT_INT_TYPE("byte", "UInt8"); + INSERT_INT_TYPE("ushort", "UInt16"); + INSERT_INT_TYPE("uint", "UInt32"); + INSERT_INT_TYPE("ulong", "UInt64"); + +#undef INSERT_INT_TYPE } // Floating point types @@ -3427,18 +3446,19 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "float"; itype.cname = itype.name; itype.proxy_name = "float"; + itype.cs_type = itype.proxy_name; { // The expected type for 'float' in ptrcall is 'double' - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; + itype.c_in = "%5%0 %1_in = %1;\n"; + itype.c_out = "%5return (%0)%1;\n"; itype.c_type = "double"; - itype.c_type_in = "float"; - itype.c_type_out = "float"; itype.c_arg_in = "&%s_in"; } - itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_type_in = itype.proxy_name; + itype.c_type_out = itype.proxy_name; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToFloat32(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromFloat(%0)"; builtin_types.insert(itype.cname, itype); // double @@ -3446,15 +3466,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "double"; itype.cname = itype.name; itype.proxy_name = "double"; - { - itype.c_type = "double"; - itype.c_type_in = "double"; - itype.c_type_out = "double"; - itype.c_arg_in = "&%s"; - } itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_type = "double"; + itype.c_arg_in = "&%s"; + itype.c_type_in = itype.proxy_name; + itype.c_type_out = itype.proxy_name; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToFloat64(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromFloat(%0)"; builtin_types.insert(itype.cname, itype); } @@ -3463,15 +3482,17 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "String"; itype.cname = itype.name; itype.proxy_name = "string"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n"; - itype.c_out = "\treturn " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n"; - itype.c_arg_in = "&%s_in"; - itype.c_type = itype.name; - itype.c_type_in = "MonoString*"; - itype.c_type_out = "MonoString*"; itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n"; + itype.c_out = "%5return " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = "godot_string"; + itype.c_type_in = itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = true; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromString(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToStringObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromString(%0)"; builtin_types.insert(itype.cname, itype); // StringName @@ -3479,17 +3500,19 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { 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"; + itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)"; + // Cannot pass null StringName to ptrcall + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_string_name"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromStringName(%1);\n"; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToStringNameObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromStringName(%0)"; builtin_types.insert(itype.cname, itype); // NodePath @@ -3497,15 +3520,18 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "NodePath"; itype.cname = itype.name; itype.proxy_name = "NodePath"; - itype.c_out = "\treturn memnew(NodePath(%1));\n"; - 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 = "NodePath." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)"; + // Cannot pass null NodePath to ptrcall + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_node_path"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToNodePathObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromNodePath(%0)"; builtin_types.insert(itype.cname, itype); // RID @@ -3513,45 +3539,45 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "RID"; itype.cname = itype.name; itype.proxy_name = "RID"; - itype.c_out = "\treturn memnew(RID(%1));\n"; - 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 = "RID." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.c_arg_in = "&%s"; + itype.c_type = itype.cs_type; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.c_type; + itype.cs_variant_to_managed = "VariantUtils.ConvertToRID(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromRID(%0)"; builtin_types.insert(itype.cname, itype); // Variant itype = TypeInterface(); itype.name = "Variant"; itype.cname = itype.name; - itype.proxy_name = "object"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_VARIANT "(%1);\n"; - itype.c_out = "\treturn " C_METHOD_MANAGED_FROM_VARIANT "(%1);\n"; - itype.c_arg_in = "&%s_in"; - itype.c_type = itype.name; - itype.c_type_in = "MonoObject*"; - itype.c_type_out = "MonoObject*"; + itype.proxy_name = "Variant"; itype.cs_type = itype.proxy_name; - itype.im_type_in = "object"; - itype.im_type_out = itype.proxy_name; + itype.c_in = "%5%0 %1_in = (%0)%1.NativeVar;\n"; + itype.c_out = "%5return Variant.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = "godot_variant"; + itype.c_type_in = itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "Variant.CreateCopyingBorrowed(%0)"; + itype.cs_managed_to_variant = "%0.CopyNativeVariant()"; 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.cs_in_expr = "%0"; + itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(in %1);\n"; + itype.c_out = "%5return " C_METHOD_MANAGED_FROM_CALLABLE "(in %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; + itype.c_type = "godot_callable"; + itype.c_type_in = "in " + itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToCallableManaged(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromCallable(%0)"; builtin_types.insert(itype.cname, itype); // Signal @@ -3559,66 +3585,65 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { 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; + itype.cs_in_expr = "%0"; + itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(in %1);\n"; + itype.c_out = "%5return " C_METHOD_MANAGED_FROM_SIGNAL "(&%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = "godot_signal"; + itype.c_type_in = "in " + itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToSignalInfo(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromSignalInfo(%0)"; builtin_types.insert(itype.cname, itype); // VarArg (fictitious type to represent variable arguments) itype = TypeInterface(); itype.name = "VarArg"; itype.cname = itype.name; - itype.proxy_name = "object[]"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MONOARRAY_TO(Array) "(%1);\n"; + itype.proxy_name = "Variant[]"; + itype.cs_type = "params Variant[]"; + itype.cs_in_expr = "%0 ?? Array.Empty<Variant>()"; + // c_type, c_in and c_arg_in are hard-coded in the generator. + // c_out and c_type_out are not applicable to VarArg. itype.c_arg_in = "&%s_in"; - itype.c_type = "Array"; - itype.c_type_in = "MonoArray*"; - itype.cs_type = "params object[]"; - itype.im_type_in = "object[]"; + itype.c_type_in = "Variant[]"; builtin_types.insert(itype.cname, itype); -#define INSERT_ARRAY_FULL(m_name, m_type, m_proxy_t) \ - { \ - itype = TypeInterface(); \ - itype.name = #m_name; \ - itype.cname = itype.name; \ - itype.proxy_name = #m_proxy_t "[]"; \ - itype.c_in = "\t%0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \ - itype.c_out = "\treturn " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \ - itype.c_arg_in = "&%s_in"; \ - itype.c_type = #m_type; \ - itype.c_type_in = "MonoArray*"; \ - itype.c_type_out = "MonoArray*"; \ - itype.cs_type = itype.proxy_name; \ - itype.im_type_in = itype.proxy_name; \ - itype.im_type_out = itype.proxy_name; \ - builtin_types.insert(itype.name, itype); \ +#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \ + { \ + itype = TypeInterface(); \ + itype.name = #m_name; \ + itype.cname = itype.name; \ + itype.proxy_name = #m_proxy_t "[]"; \ + itype.cs_type = itype.proxy_name; \ + itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \ + itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \ + itype.c_arg_in = "&%s_in"; \ + itype.c_type = #m_managed_type; \ + itype.c_type_in = itype.proxy_name; \ + itype.c_type_out = itype.proxy_name; \ + itype.c_type_is_disposable_struct = true; \ + itype.cs_variant_to_managed = "VariantUtils.ConvertAs%2ToSystemArray(%0)"; \ + itype.cs_managed_to_variant = "VariantUtils.CreateFrom%2(%0)"; \ + builtin_types.insert(itype.name, itype); \ } -#define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t) +#define INSERT_ARRAY(m_type, m_managed_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_managed_type, m_proxy_t) - INSERT_ARRAY(PackedInt32Array, int); - INSERT_ARRAY(PackedInt64Array, long); - INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, byte); + INSERT_ARRAY(PackedInt32Array, godot_packed_int32_array, int); + INSERT_ARRAY(PackedInt64Array, godot_packed_int64_array, long); + INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, godot_packed_byte_array, byte); - INSERT_ARRAY(PackedFloat32Array, float); - INSERT_ARRAY(PackedFloat64Array, double); + INSERT_ARRAY(PackedFloat32Array, godot_packed_float32_array, float); + INSERT_ARRAY(PackedFloat64Array, godot_packed_float64_array, double); - INSERT_ARRAY(PackedStringArray, string); + INSERT_ARRAY(PackedStringArray, godot_packed_string_array, string); - INSERT_ARRAY(PackedColorArray, Color); - INSERT_ARRAY(PackedVector2Array, Vector2); - INSERT_ARRAY(PackedVector3Array, Vector3); + INSERT_ARRAY(PackedColorArray, godot_packed_color_array, Color); + INSERT_ARRAY(PackedVector2Array, godot_packed_vector2_array, Vector2); + INSERT_ARRAY(PackedVector3Array, godot_packed_vector3_array, Vector3); #undef INSERT_ARRAY @@ -3628,15 +3653,24 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cname = itype.name; itype.proxy_name = itype.name; itype.type_parameter_count = 1; - itype.c_out = "\treturn memnew(Array(%1));\n"; - itype.c_type = itype.name; - itype.c_type_in = itype.c_type + "*"; - itype.c_type_out = itype.c_type + "*"; itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; - itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue"; + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_array"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToArrayObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)"; + builtin_types.insert(itype.cname, itype); + + // Array_@generic + // Re-use Array's itype + itype.name = "Array_@generic"; + itype.cname = itype.name; + itype.cs_out = "%5return new %2(%0(%1));"; builtin_types.insert(itype.cname, itype); // Dictionary @@ -3645,15 +3679,24 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cname = itype.name; itype.proxy_name = itype.name; itype.type_parameter_count = 2; - itype.c_out = "\treturn memnew(Dictionary(%1));\n"; - itype.c_type = itype.name; - itype.c_type_in = itype.c_type + "*"; - itype.c_type_out = itype.c_type + "*"; itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; - itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue"; + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_dictionary"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionaryObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)"; + builtin_types.insert(itype.cname, itype); + + // Dictionary_@generic + // Re-use Dictionary's itype + itype.name = "Dictionary_@generic"; + itype.cname = itype.name; + itype.cs_out = "%5return new %2(%0(%1));"; builtin_types.insert(itype.cname, itype); // void (fictitious type to represent the return type of methods that do not return anything) @@ -3661,12 +3704,10 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "void"; itype.cname = itype.name; itype.proxy_name = itype.name; - itype.c_type = itype.name; + itype.cs_type = itype.proxy_name; + itype.c_type = itype.proxy_name; itype.c_type_in = itype.c_type; itype.c_type_out = itype.c_type; - itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); } @@ -3721,6 +3762,8 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.cname = ienum.cname; enum_itype.proxy_name = enum_itype.name; TypeInterface::postsetup_enum_type(enum_itype); + enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; + enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); int prefix_length = _determine_enum_prefix(ienum); @@ -3753,6 +3796,8 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.cname = enum_cname; enum_itype.proxy_name = enum_itype.name; TypeInterface::postsetup_enum_type(enum_itype); + enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; + enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); } } @@ -3791,21 +3836,18 @@ void BindingsGenerator::_initialize() { // Generate internal calls (after populating type interfaces and global constants) - core_custom_icalls.clear(); - editor_custom_icalls.clear(); - for (const KeyValue<StringName, TypeInterface> &E : obj_types) { - _generate_method_icalls(E.value); + const TypeInterface &itype = E.value; + Error err = _populate_method_icalls_table(itype); + ERR_FAIL_COND_MSG(err != OK, "Failed to generate icalls table for type: " + itype.name); } initialized = true; } static String generate_all_glue_option = "--generate-mono-glue"; -static String generate_cs_glue_option = "--generate-mono-cs-glue"; -static String generate_cpp_glue_option = "--generate-mono-cpp-glue"; -static void handle_cmdline_options(String glue_dir_path, String cs_dir_path, String cpp_dir_path) { +static void handle_cmdline_options(String glue_dir_path) { BindingsGenerator bindings_generator; bindings_generator.set_log_print_enabled(true); @@ -3814,43 +3856,25 @@ static void handle_cmdline_options(String glue_dir_path, String cs_dir_path, Str return; } - if (glue_dir_path.length()) { - if (bindings_generator.generate_glue(glue_dir_path) != OK) { - ERR_PRINT(generate_all_glue_option + ": Failed to generate the C++ glue."); - } + CRASH_COND(glue_dir_path.is_empty()); - if (bindings_generator.generate_cs_api(glue_dir_path.plus_file(API_SOLUTION_NAME)) != OK) { - ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API."); - } - } - - if (cs_dir_path.length()) { - if (bindings_generator.generate_cs_api(cs_dir_path) != OK) { - ERR_PRINT(generate_cs_glue_option + ": Failed to generate the C# API."); - } + if (bindings_generator.generate_cs_api(glue_dir_path.plus_file(API_SOLUTION_NAME)) != OK) { + ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API."); } +} - if (cpp_dir_path.length()) { - if (bindings_generator.generate_glue(cpp_dir_path) != OK) { - ERR_PRINT(generate_cpp_glue_option + ": Failed to generate the C++ glue."); - } - } +static void cleanup_and_exit_godot() { + // Exit once done + Main::cleanup(true); + ::exit(0); } void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { - const int NUM_OPTIONS = 2; - String glue_dir_path; - String cs_dir_path; - String cpp_dir_path; - - int options_left = NUM_OPTIONS; - - bool exit_godot = false; const List<String>::Element *elem = p_cmdline_args.front(); - while (elem && options_left) { + while (elem) { if (elem->get() == generate_all_glue_option) { const List<String>::Element *path_elem = elem->next(); @@ -3859,48 +3883,20 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) elem = elem->next(); } else { ERR_PRINT(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue')."); - exit_godot = true; - } - - --options_left; - } else if (elem->get() == generate_cs_glue_option) { - const List<String>::Element *path_elem = elem->next(); - - if (path_elem) { - cs_dir_path = path_elem->get(); - elem = elem->next(); - } else { - ERR_PRINT(generate_cs_glue_option + ": No output directory specified."); - exit_godot = true; + // Exit once done with invalid command line arguments + cleanup_and_exit_godot(); } - --options_left; - } else if (elem->get() == generate_cpp_glue_option) { - const List<String>::Element *path_elem = elem->next(); - - if (path_elem) { - cpp_dir_path = path_elem->get(); - elem = elem->next(); - } else { - ERR_PRINT(generate_cpp_glue_option + ": No output directory specified."); - exit_godot = true; - } - - --options_left; + break; } elem = elem->next(); } - if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) { - handle_cmdline_options(glue_dir_path, cs_dir_path, cpp_dir_path); - exit_godot = true; - } - - if (exit_godot) { + if (glue_dir_path.length()) { + handle_cmdline_options(glue_dir_path); // Exit once done - Main::cleanup(true); - ::exit(0); + cleanup_and_exit_godot(); } } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index ee170e4558..c1295385dc 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -229,6 +229,23 @@ class BindingsGenerator { bool is_ref_counted = false; /** + * Determines whether the native return value of this type must be disposed + * by the generated internal call (think of `godot_string`, whose destructor + * must be called). Some structs that are disposable may still disable this + * flag if the ownership is transferred. + */ + bool c_type_is_disposable_struct = false; + + /** + * Determines whether the native return value of this type must be zero initialized + * before its address is passed to ptrcall. This is required for types whose destructor + * is called before being assigned the return value by `PtrToArg::encode`, e.g.: + * Array, Dictionary, String, StringName, Variant. + * It's not necessary to set this to `true` if [c_type_is_disposable_struct] is already `true`. + */ + bool c_ret_needs_default_initialization = false; + + /** * Used only by Object-derived types. * Determines if this type is not abstract (incomplete). * e.g.: CanvasItem cannot be instantiated. @@ -242,31 +259,34 @@ class BindingsGenerator { */ bool memory_own = false; - /** - * This must be set to true for any struct bigger than 32-bits. Those cannot be passed/returned by value - * with internal calls, so we must use pointers instead. Returns must be replace with out parameters. - * In this case, [c_out] and [cs_out] must have a different format, explained below. - * The Mono IL interpreter icall trampolines don't support passing structs bigger than 32-bits by value (at least not on WASM). - */ - bool ret_as_byref_arg = false; - // !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name] // !! When renaming those fields, make sure to rename their references in the comments // --- C INTERFACE --- - static const char *DEFAULT_VARARG_C_IN; + /** + * One or more statements that transform the parameter before being passed as argument of a ptrcall. + * If the statement adds a local that must be passed as the argument instead of the parameter, + * the expression with the name of that local must be specified with [c_arg_in]. + * Formatting elements: + * %0: [c_type] of the parameter + * %1: name of the parameter + * %2-4: reserved + * %5: indentation text + */ + String c_in; /** - * One or more statements that manipulate the parameter before being passed as argument of a ptrcall. + * One or more statements that transform the parameter before being passed as argument of a vararg call. * If the statement adds a local that must be passed as the argument instead of the parameter, * the name of that local must be specified with [c_arg_in]. - * For variadic methods, this field is required and, if empty, [DEFAULT_VARARG_C_IN] is used instead. * Formatting elements: * %0: [c_type] of the parameter * %1: name of the parameter + * %2-4: reserved + * %5: indentation text */ - String c_in; + String c_in_vararg; /** * Determines the expression that will be passed as argument to ptrcall. @@ -291,7 +311,8 @@ class BindingsGenerator { * %0: [c_type_out] of the return type * %1: name of the variable to be returned * %2: [name] of the return type - * %3: name of the parameter that must be assigned the return value + * %3-4: reserved + * %5: indentation text */ String c_out; @@ -327,7 +348,21 @@ class BindingsGenerator { * An expression that overrides the way the parameter is passed to the internal call. * If empty, the parameter is passed as is. * Formatting elements: - * %0 or %s: name of the parameter + * %0: name of the parameter + * %1: [c_type] of the parameter + */ + String cs_in_expr; + bool cs_in_expr_is_unsafe = false; + + /** + * One or more statements that transform the parameter before being passed to the internal call. + * If the statement adds a local that must be passed as the argument instead of the parameter, + * the expression with the name of that local must be specified with [cs_in_expr]. + * Formatting elements: + * %0: [c_type] of the parameter + * %1: name of the parameter + * %2-4: reserved + * %5: indentation text */ String cs_in; @@ -338,7 +373,9 @@ class BindingsGenerator { * %0: internal method name * %1: internal method call arguments without surrounding parenthesis * %2: [cs_type] of the return type - * %3: [im_type_out] of the return type + * %3: [c_type_out] of the return type + * %4: reserved + * %5: indentation text */ String cs_out; @@ -349,14 +386,20 @@ class BindingsGenerator { String cs_type; /** - * Type used for parameters of internal call methods. + * Formatting elements: + * %0: input expression of type `in godot_variant` + * %1: [cs_type] of this type + * %2: [name] of this type */ - String im_type_in; + String cs_variant_to_managed; /** - * Type used for the return type of internal call methods. + * Formatting elements: + * %0: input expression + * %1: [cs_type] of this type + * %2: [name] of this type */ - String im_type_out; + String cs_managed_to_variant; const DocData::ClassDoc *class_doc = nullptr; @@ -366,6 +409,8 @@ class BindingsGenerator { List<MethodInterface> methods; List<SignalInterface> signals_; + bool has_virtual_methods = false; + const MethodInterface *find_method_by_name(const StringName &p_cname) const { for (const MethodInterface &E : methods) { if (E.cname == p_cname) { @@ -432,8 +477,8 @@ class BindingsGenerator { itype.c_type = itype.name; itype.cs_type = itype.proxy_name; - itype.im_type_in = "ref " + itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_type_in = itype.proxy_name + "*"; + itype.c_type_out = itype.proxy_name; itype.class_doc = &EditorHelp::get_doc_data()->class_list[itype.proxy_name]; } @@ -467,65 +512,27 @@ class BindingsGenerator { return itype; } - static void create_placeholder_type(TypeInterface &r_itype, const StringName &p_cname) { - r_itype.name = p_cname; - r_itype.cname = p_cname; - r_itype.proxy_name = r_itype.name; - - r_itype.c_type = r_itype.name; - r_itype.c_type_in = "MonoObject*"; - r_itype.c_type_out = "MonoObject*"; - r_itype.cs_type = r_itype.proxy_name; - r_itype.im_type_in = r_itype.proxy_name; - r_itype.im_type_out = r_itype.proxy_name; - } - - static void postsetup_enum_type(TypeInterface &r_enum_itype) { - // C interface for enums is the same as that of 'uint32_t'. Remember to apply - // any of the changes done here to the 'uint32_t' type interface as well. - - r_enum_itype.c_arg_in = "&%s_in"; - { - // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. - r_enum_itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - r_enum_itype.c_out = "\treturn (%0)%1;\n"; - r_enum_itype.c_type = "int64_t"; - } - r_enum_itype.c_type_in = "int32_t"; - r_enum_itype.c_type_out = r_enum_itype.c_type_in; - - r_enum_itype.cs_type = r_enum_itype.proxy_name; - r_enum_itype.cs_in = "(int)%s"; - r_enum_itype.cs_out = "return (%2)%0(%1);"; - r_enum_itype.im_type_in = "int"; - r_enum_itype.im_type_out = "int"; - r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name]; - } + static void postsetup_enum_type(TypeInterface &r_enum_itype); TypeInterface() {} }; struct InternalCall { String name; - String im_type_out; // Return type for the C# method declaration. Also used as companion of [unique_siq] - String im_sig; // Signature for the C# method declaration String unique_sig; // Unique signature to avoid duplicates in containers bool editor_only = false; - InternalCall() {} + bool is_vararg = false; + bool is_static = false; + TypeReference return_type; + List<TypeReference> argument_types; - InternalCall(const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) { - name = p_name; - im_type_out = p_im_type_out; - im_sig = p_im_sig; - unique_sig = p_unique_sig; - editor_only = false; - } + _FORCE_INLINE_ int get_arguments_count() const { return argument_types.size(); } + + InternalCall() {} - InternalCall(ClassDB::APIType api_type, const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) { + InternalCall(ClassDB::APIType api_type, const String &p_name, const String &p_unique_sig = String()) { name = p_name; - im_type_out = p_im_type_out; - im_sig = p_im_sig; unique_sig = p_unique_sig; editor_only = api_type == ClassDB::API_EDITOR; } @@ -540,7 +547,6 @@ class BindingsGenerator { HashMap<StringName, TypeInterface> obj_types; - HashMap<StringName, TypeInterface> placeholder_types; HashMap<StringName, TypeInterface> builtin_types; HashMap<StringName, TypeInterface> enum_types; @@ -548,13 +554,9 @@ class BindingsGenerator { List<ConstantInterface> global_constants; List<InternalCall> method_icalls; + /// Stores the unique internal calls from [method_icalls] that are assigned to each method. HashMap<const MethodInterface *, const InternalCall *> method_icalls_map; - List<const InternalCall *> generated_icall_funcs; - - List<InternalCall> core_custom_icalls; - List<InternalCall> editor_custom_icalls; - HashMap<StringName, List<StringName>> blacklisted_methods; void _initialize_blacklisted_methods(); @@ -571,6 +573,8 @@ class BindingsGenerator { StringName type_String = StaticCString::create("String"); StringName type_StringName = StaticCString::create("StringName"); StringName type_NodePath = StaticCString::create("NodePath"); + StringName type_Array_generic = StaticCString::create("Array_@generic"); + StringName type_Dictionary_generic = StaticCString::create("Dictionary_@generic"); StringName type_at_GlobalScope = StaticCString::create("@GlobalScope"); StringName enum_Error = StaticCString::create("Error"); @@ -595,12 +599,14 @@ class BindingsGenerator { StringName type_Vector4i = StaticCString::create("Vector4i"); // Object not included as it must be checked for all derived classes - static constexpr int nullable_types_count = 17; + static constexpr int nullable_types_count = 18; StringName nullable_types[nullable_types_count] = { type_String, type_StringName, type_NodePath, + type_Array_generic, + type_Dictionary_generic, StaticCString::create(_STR(Array)), StaticCString::create(_STR(Dictionary)), StaticCString::create(_STR(Callable)), @@ -636,17 +642,6 @@ class BindingsGenerator { NameCache name_cache; - const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) { - const List<InternalCall>::Element *it = p_list.front(); - while (it) { - if (it->get().name == p_name) { - return it; - } - it = it->next(); - } - return nullptr; - } - const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const { for (const ConstantInterface &E : p_constants) { if (E.name == p_name) { @@ -657,18 +652,38 @@ class BindingsGenerator { return nullptr; } - inline String get_unique_sig(const TypeInterface &p_type) { - if (p_type.is_ref_counted) { - return "Ref"; - } else if (p_type.is_object_type) { + inline String get_arg_unique_sig(const TypeInterface &p_type) { + // For parameters, we treat reference and non-reference derived types the same. + if (p_type.is_object_type) { return "Obj"; } else if (p_type.is_enum) { return "int"; + } else if (p_type.cname == name_cache.type_Array_generic) { + return "Array"; + } else if (p_type.cname == name_cache.type_Dictionary_generic) { + return "Dictionary"; } return p_type.name; } + inline String get_ret_unique_sig(const TypeInterface *p_type) { + // Reference derived return types are treated differently. + if (p_type->is_ref_counted) { + return "Ref"; + } else if (p_type->is_object_type) { + return "Obj"; + } else if (p_type->is_enum) { + return "int"; + } else if (p_type->cname == name_cache.type_Array_generic) { + return "Array"; + } else if (p_type->cname == name_cache.type_Dictionary_generic) { + return "Dictionary"; + } + + return p_type->name; + } + String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype); void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts); @@ -682,10 +697,9 @@ class BindingsGenerator { int _determine_enum_prefix(const EnumInterface &p_ienum); void _apply_prefix_to_enum_constants(EnumInterface &p_ienum, int p_prefix_length); - void _generate_method_icalls(const TypeInterface &p_itype); + Error _populate_method_icalls_table(const TypeInterface &p_itype); const TypeInterface *_get_type_or_null(const TypeReference &p_typeref); - const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref); const String _get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters); @@ -706,11 +720,11 @@ class BindingsGenerator { 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); + Error _generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output); + void _generate_array_extensions(StringBuilder &p_output); void _generate_global_constants(StringBuilder &p_output); - Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output); - Error _save_file(const String &p_path, const StringBuilder &p_content); void _log(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; @@ -721,15 +735,12 @@ public: Error generate_cs_core_project(const String &p_proj_dir); Error generate_cs_editor_project(const String &p_proj_dir); Error generate_cs_api(const String &p_output_dir); - Error generate_glue(const String &p_output_dir); _FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; } _FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } _FORCE_INLINE_ bool is_initialized() { return initialized; } - static uint32_t get_version(); - static void handle_cmdline_args(const List<String> &p_cmdline_args); BindingsGenerator() { diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index f830c7ffe1..1ef78c3ac2 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -46,179 +46,81 @@ #include "main/main.h" #include "../csharp_script.h" -#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_marshal.h" #include "../utils/macos_utils.h" #include "code_completion.h" -#include "godotsharp_export.h" -MonoString *godot_icall_GodotSharpDirs_ResDataDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResConfigDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResTempDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_MonoUserDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir()); -} +#include "../interop_types.h" -MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir()); -#else - return nullptr; +#ifdef __cplusplus +extern "C" { #endif -} -MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir()); -#else - return nullptr; -#endif +void godot_icall_GodotSharpDirs_ResMetadataDir(godot_string *r_dest) { + memnew_placement(r_dest, String(GodotSharpDirs::get_res_metadata_dir())); } -MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path()); -#else - return nullptr; -#endif +void godot_icall_GodotSharpDirs_MonoUserDir(godot_string *r_dest) { + memnew_placement(r_dest, String(GodotSharpDirs::get_mono_user_dir())); } -MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() { +void godot_icall_GodotSharpDirs_BuildLogsDirs(godot_string *r_dest) { #ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path()); + memnew_placement(r_dest, String(GodotSharpDirs::get_build_logs_dir())); #else return nullptr; #endif } -MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() { +void godot_icall_GodotSharpDirs_DataEditorToolsDir(godot_string *r_dest) { #ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir()); + memnew_placement(r_dest, String(GodotSharpDirs::get_data_editor_tools_dir())); #else return nullptr; #endif } -MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir()); -#else - return nullptr; -#endif -} - -MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() { -#ifdef WINDOWS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir()); -#else - return nullptr; -#endif -} - -void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) { - String task = GDMonoMarshal::mono_string_to_godot(p_task); - String label = GDMonoMarshal::mono_string_to_godot(p_label); +void godot_icall_EditorProgress_Create(const godot_string *p_task, const godot_string *p_label, int32_t p_amount, bool p_can_cancel) { + String task = *reinterpret_cast<const String *>(p_task); + String label = *reinterpret_cast<const String *>(p_label); EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel); } -void godot_icall_EditorProgress_Dispose(MonoString *p_task) { - String task = GDMonoMarshal::mono_string_to_godot(p_task); +void godot_icall_EditorProgress_Dispose(const godot_string *p_task) { + String task = *reinterpret_cast<const String *>(p_task); EditorNode::progress_end_task(task); } -MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) { - String task = GDMonoMarshal::mono_string_to_godot(p_task); - String state = GDMonoMarshal::mono_string_to_godot(p_state); +bool godot_icall_EditorProgress_Step(const godot_string *p_task, const godot_string *p_state, int32_t p_step, bool p_force_refresh) { + String task = *reinterpret_cast<const String *>(p_task); + String state = *reinterpret_cast<const String *>(p_state); return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); } -uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies, - MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) { - Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies); - String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config); - String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir); - Dictionary assembly_dependencies = GDMonoMarshal::mono_object_to_variant(r_assembly_dependencies); - - return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, assembly_dependencies); -} - -MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) { - String config = GDMonoMarshal::mono_string_to_godot(p_config); - String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(config); - return GDMonoMarshal::mono_string_from_godot(error_str); -} - -MonoString *godot_icall_Internal_FullExportTemplatesDir() { +void godot_icall_Internal_FullExportTemplatesDir(godot_string *r_dest) { String full_templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG); - return GDMonoMarshal::mono_string_from_godot(full_templates_dir); -} - -MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { - String path = GDMonoMarshal::mono_string_to_godot(p_path); - return GDMonoMarshal::mono_string_from_godot(path.simplify_path()); + memnew_placement(r_dest, String(full_templates_dir)); } -MonoBoolean godot_icall_Internal_IsMacOSAppBundleInstalled(MonoString *p_bundle_id) { +bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle_id) { #ifdef MACOS_ENABLED - String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); - return (MonoBoolean)macos_is_app_bundle_installed(bundle_id); + String bundle_id = *reinterpret_cast<const String *>(p_bundle_id); + return (bool)macos_is_app_bundle_installed(bundle_id); #else (void)p_bundle_id; // UNUSED - return (MonoBoolean) false; + return (bool)false; #endif } -MonoBoolean godot_icall_Internal_GodotIs32Bits() { +bool godot_icall_Internal_GodotIs32Bits() { return sizeof(void *) == 4; } -MonoBoolean godot_icall_Internal_GodotIsRealTDouble() { +bool godot_icall_Internal_GodotIsRealTDouble() { #ifdef REAL_T_IS_DOUBLE - return (MonoBoolean) true; + return (bool)true; #else - return (MonoBoolean) false; + return (bool)false; #endif } @@ -226,23 +128,15 @@ void godot_icall_Internal_GodotMainIteration() { Main::iteration(); } -uint64_t godot_icall_Internal_GetCoreApiHash() { - return ClassDB::get_api_hash(ClassDB::API_CORE); -} - -uint64_t godot_icall_Internal_GetEditorApiHash() { - return ClassDB::get_api_hash(ClassDB::API_EDITOR); -} - -MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() { +bool godot_icall_Internal_IsAssembliesReloadingNeeded() { #ifdef GD_MONO_HOT_RELOAD - return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed(); + return (bool)CSharpLanguage::get_singleton()->is_assembly_reloading_needed(); #else - return (MonoBoolean) false; + return (bool)false; #endif } -void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) { +void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD mono_bind::GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload); #endif @@ -252,24 +146,15 @@ void godot_icall_Internal_EditorDebuggerNodeReloadScripts() { EditorDebuggerNode::get_singleton()->reload_scripts(); } -MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) { - Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource); - return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus); +bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, int32_t p_col, bool p_grab_focus) { + Ref<Resource> resource = p_resource; + return (bool)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus); } void godot_icall_Internal_EditorNodeShowScriptScreen() { EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); } -MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { -#ifdef WINDOWS_ENABLED - String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; - return GDMonoMarshal::mono_string_from_godot(install_root_dir); -#else - return nullptr; -#endif -} - void godot_icall_Internal_EditorRunPlay() { EditorNode::get_singleton()->run_play(); } @@ -285,114 +170,93 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { } } -MonoArray *godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, MonoString *p_script_file) { - String script_file = GDMonoMarshal::mono_string_to_godot(p_script_file); +void godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, const godot_string *p_script_file, godot_packed_array *r_ret) { + String script_file = *reinterpret_cast<const String *>(p_script_file); PackedStringArray suggestions = gdmono::get_code_completion((gdmono::CompletionKind)p_kind, script_file); - return GDMonoMarshal::PackedStringArray_to_mono_array(suggestions); + memnew_placement(r_ret, PackedStringArray(suggestions)); } float godot_icall_Globals_EditorScale() { return EDSCALE; } -MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); - Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); +void godot_icall_Globals_GlobalDef(const godot_string *p_setting, const godot_variant *p_default_value, bool p_restart_if_changed, godot_variant *r_result) { + String setting = *reinterpret_cast<const String *>(p_setting); + Variant default_value = *reinterpret_cast<const Variant *>(p_default_value); Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); - return GDMonoMarshal::variant_to_mono_object(result); + memnew_placement(r_result, Variant(result)); } -MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); - Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); +void godot_icall_Globals_EditorDef(const godot_string *p_setting, const godot_variant *p_default_value, bool p_restart_if_changed, godot_variant *r_result) { + String setting = *reinterpret_cast<const String *>(p_setting); + Variant default_value = *reinterpret_cast<const Variant *>(p_default_value); Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed); - return GDMonoMarshal::variant_to_mono_object(result); + memnew_placement(r_result, Variant(result)); } -MonoObject *godot_icall_Globals_EditorShortcut(MonoString *p_setting) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); +void godot_icall_Globals_EditorShortcut(const godot_string *p_setting, godot_variant *r_result) { + String setting = *reinterpret_cast<const String *>(p_setting); Ref<Shortcut> result = ED_GET_SHORTCUT(setting); - return GDMonoMarshal::variant_to_mono_object(result); + memnew_placement(r_result, Variant(result)); } -MonoString *godot_icall_Globals_TTR(MonoString *p_text) { - String text = GDMonoMarshal::mono_string_to_godot(p_text); - return GDMonoMarshal::mono_string_from_godot(TTR(text)); +void godot_icall_Globals_TTR(const godot_string *p_text, godot_string *r_dest) { + String text = *reinterpret_cast<const String *>(p_text); + memnew_placement(r_dest, String(TTR(text))); } -MonoString *godot_icall_Utils_OS_GetPlatformName() { +void godot_icall_Utils_OS_GetPlatformName(godot_string *r_dest) { String os_name = OS::get_singleton()->get_name(); - return GDMonoMarshal::mono_string_from_godot(os_name); + memnew_placement(r_dest, String(os_name)); } -MonoBoolean godot_icall_Utils_OS_UnixFileHasExecutableAccess(MonoString *p_file_path) { +bool godot_icall_Utils_OS_UnixFileHasExecutableAccess(const godot_string *p_file_path) { #ifdef UNIX_ENABLED - String file_path = GDMonoMarshal::mono_string_to_godot(p_file_path); + String file_path = *reinterpret_cast<const String *>(p_file_path); return access(file_path.utf8().get_data(), X_OK) == 0; #else ERR_FAIL_V(false); #endif } -void register_editor_internal_calls() { - // GodotSharpDirs - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", godot_icall_GodotSharpDirs_ResDataDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", godot_icall_GodotSharpDirs_ResMetadataDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", godot_icall_GodotSharpDirs_ResAssembliesBaseDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", godot_icall_GodotSharpDirs_ResAssembliesDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", godot_icall_GodotSharpDirs_ResConfigDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", godot_icall_GodotSharpDirs_ResTempDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", godot_icall_GodotSharpDirs_ResTempAssembliesDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", godot_icall_GodotSharpDirs_MonoUserDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", godot_icall_GodotSharpDirs_MonoLogsDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", godot_icall_GodotSharpDirs_MonoSolutionsDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", godot_icall_GodotSharpDirs_BuildLogsDirs); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", godot_icall_GodotSharpDirs_ProjectSlnPath); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", godot_icall_GodotSharpDirs_ProjectCsProjPath); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", godot_icall_GodotSharpDirs_DataEditorToolsDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", godot_icall_GodotSharpDirs_DataMonoEtcDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", godot_icall_GodotSharpDirs_DataMonoLibDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", godot_icall_GodotSharpDirs_DataMonoBinDir); - - // EditorProgress - GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", godot_icall_EditorProgress_Create); - GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose); - GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step); - - // ExportPlugin - GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies); - - // Internals - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_FullExportTemplatesDir", godot_icall_Internal_FullExportTemplatesDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", godot_icall_Internal_SimplifyGodotPath); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsMacOSAppBundleInstalled", godot_icall_Internal_IsMacOSAppBundleInstalled); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", godot_icall_Internal_GodotIs32Bits); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", godot_icall_Internal_GodotIsRealTDouble); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", godot_icall_Internal_GodotMainIteration); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", godot_icall_Internal_GetCoreApiHash); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", godot_icall_Internal_GetEditorApiHash); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", godot_icall_Internal_IsAssembliesReloadingNeeded); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", godot_icall_Internal_ReloadAssemblies); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", godot_icall_Internal_CodeCompletionRequest); - - // Globals - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", godot_icall_Globals_EditorScale); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", godot_icall_Globals_GlobalDef); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", godot_icall_Globals_EditorDef); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorShortcut", godot_icall_Globals_EditorShortcut); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_TTR", godot_icall_Globals_TTR); - - // Utils.OS - GDMonoUtils::add_internal_call("GodotTools.Utils.OS::GetPlatformName", godot_icall_Utils_OS_GetPlatformName); - GDMonoUtils::add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", godot_icall_Utils_OS_UnixFileHasExecutableAccess); +#ifdef __cplusplus +} +#endif + +// The order in this array must match the declaration order of +// the methods in 'GodotTools/Internals/Internal.cs'. +static const void *unmanaged_callbacks[]{ + (void *)godot_icall_GodotSharpDirs_ResMetadataDir, + (void *)godot_icall_GodotSharpDirs_MonoUserDir, + (void *)godot_icall_GodotSharpDirs_BuildLogsDirs, + (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir, + (void *)godot_icall_EditorProgress_Create, + (void *)godot_icall_EditorProgress_Dispose, + (void *)godot_icall_EditorProgress_Step, + (void *)godot_icall_Internal_FullExportTemplatesDir, + (void *)godot_icall_Internal_IsMacOSAppBundleInstalled, + (void *)godot_icall_Internal_GodotIs32Bits, + (void *)godot_icall_Internal_GodotIsRealTDouble, + (void *)godot_icall_Internal_GodotMainIteration, + (void *)godot_icall_Internal_IsAssembliesReloadingNeeded, + (void *)godot_icall_Internal_ReloadAssemblies, + (void *)godot_icall_Internal_EditorDebuggerNodeReloadScripts, + (void *)godot_icall_Internal_ScriptEditorEdit, + (void *)godot_icall_Internal_EditorNodeShowScriptScreen, + (void *)godot_icall_Internal_EditorRunPlay, + (void *)godot_icall_Internal_EditorRunStop, + (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts, + (void *)godot_icall_Internal_CodeCompletionRequest, + (void *)godot_icall_Globals_EditorScale, + (void *)godot_icall_Globals_GlobalDef, + (void *)godot_icall_Globals_EditorDef, + (void *)godot_icall_Globals_EditorShortcut, + (void *)godot_icall_Globals_TTR, + (void *)godot_icall_Utils_OS_GetPlatformName, + (void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess, +}; + +const void **godotsharp::get_editor_interop_funcs(int32_t &r_size) { + r_size = sizeof(unmanaged_callbacks); + return unmanaged_callbacks; } diff --git a/modules/mono/editor/editor_internal_calls.h b/modules/mono/editor/editor_internal_calls.h index 8262ac211a..35391f1f04 100644 --- a/modules/mono/editor/editor_internal_calls.h +++ b/modules/mono/editor/editor_internal_calls.h @@ -31,6 +31,10 @@ #ifndef EDITOR_INTERNAL_CALLS_H #define EDITOR_INTERNAL_CALLS_H -void register_editor_internal_calls(); +#include "core/typedefs.h" + +namespace godotsharp { +const void **get_editor_interop_funcs(int32_t &r_size); +} #endif // EDITOR_INTERNAL_CALLS_H diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp deleted file mode 100644 index f9ea403334..0000000000 --- a/modules/mono/editor/godotsharp_export.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/*************************************************************************/ -/* godotsharp_export.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "godotsharp_export.h" - -#include <mono/metadata/image.h> - -#include "core/config/project_settings.h" -#include "core/io/file_access_pack.h" -#include "core/os/os.h" - -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_assembly.h" -#include "../mono_gd/gd_mono_cache.h" -#include "../utils/macros.h" - -namespace GodotSharpExport { - -MonoAssemblyName *new_mono_assembly_name() { - // Mono has no public API to create an empty MonoAssemblyName and the struct is private. - // As such the only way to create it is with a stub name and then clear it. - - MonoAssemblyName *aname = mono_assembly_name_new("stub"); - CRASH_COND(aname == nullptr); - mono_assembly_name_free(aname); // Frees the string fields, not the struct - return aname; -} - -struct AssemblyRefInfo { - String name; - uint16_t major = 0; - uint16_t minor = 0; - uint16_t build = 0; - uint16_t revision = 0; -}; - -AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) { - const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); - - uint32_t cols[MONO_ASSEMBLYREF_SIZE]; - - mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE); - - return { - String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])), - (uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION], - (uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION], - (uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER], - (uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER] - }; -} - -Error get_assembly_dependencies(GDMonoAssembly *p_assembly, MonoAssemblyName *reusable_aname, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) { - MonoImage *image = p_assembly->get_image(); - - for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { - AssemblyRefInfo ref_info = get_assemblyref_name(image, i); - - const String &ref_name = ref_info.name; - - if (r_assembly_dependencies.has(ref_name)) { - continue; - } - - mono_assembly_get_assemblyref(image, i, reusable_aname); - - GDMonoAssembly *ref_assembly = nullptr; - if (!GDMono::get_singleton()->load_assembly(ref_name, reusable_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) { - ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); - } - - r_assembly_dependencies[ref_name] = ref_assembly->get_path(); - - Error err = get_assembly_dependencies(ref_assembly, reusable_aname, p_search_dirs, r_assembly_dependencies); - ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); - } - - return OK; -} - -Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, - const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_assembly_dependencies) { - MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport"); - ERR_FAIL_NULL_V(export_domain, FAILED); - _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); - - _GDMONO_SCOPE_DOMAIN_(export_domain); - - Vector<String> search_dirs; - GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir); - - if (p_custom_bcl_dir.length()) { - // Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory. - r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path(); - } - - for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) { - String assembly_name = *key; - String assembly_path = p_initial_assemblies[*key]; - - 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 + "'."); - - MonoAssemblyName *reusable_aname = new_mono_assembly_name(); - SCOPE_EXIT { mono_free(reusable_aname); }; - - Error err = get_assembly_dependencies(assembly, reusable_aname, search_dirs, r_assembly_dependencies); - if (err != OK) { - return err; - } - } - - return OK; -} -} // namespace GodotSharpExport diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h deleted file mode 100644 index 60620b5f4d..0000000000 --- a/modules/mono/editor/godotsharp_export.h +++ /dev/null @@ -1,48 +0,0 @@ -/*************************************************************************/ -/* godotsharp_export.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GODOTSHARP_EXPORT_H -#define GODOTSHARP_EXPORT_H - -#include "core/error/error_list.h" -#include "core/string/ustring.h" -#include "core/variant/dictionary.h" - -#include "../mono_gd/gd_mono_header.h" - -namespace GodotSharpExport { - -Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); - -Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, - const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_assembly_dependencies); -} // namespace GodotSharpExport - -#endif // GODOTSHARP_EXPORT_H diff --git a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs index 2ca81ab7cd..c34f1a17f3 100644 --- a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs +++ b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs @@ -9,7 +9,7 @@ public partial class _CLASS_ : _BASE_ public const float JumpVelocity = -400.0f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. - public float gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity"); + public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle(); public override void _PhysicsProcess(float delta) { diff --git a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs index a6935fe497..069908c426 100644 --- a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs +++ b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs @@ -9,7 +9,7 @@ public partial class _CLASS_ : _BASE_ public const float JumpVelocity = 4.5f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. - public float gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity"); + public float gravity = ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle(); public override void _PhysicsProcess(float delta) { @@ -26,7 +26,7 @@ public partial class _CLASS_ : _BASE_ // Get the input direction and handle the movement/deceleration. // As good practice, you should replace UI actions with custom gameplay actions. Vector2 inputDir = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); - Vector3 direction = Transform.basis.Xform(new Vector3(inputDir.x, 0, inputDir.y)).Normalized(); + Vector3 direction = (Transform.basis * new Vector3(inputDir.x, 0, inputDir.y)).Normalized(); if (direction != Vector3.Zero) { velocity.x = direction.x * Speed; diff --git a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs index a1b93e7daa..bb482e0d6a 100644 --- a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs +++ b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs @@ -55,7 +55,7 @@ public partial class VisualShaderNode_CLASS_ : _BASE_ return 0; } - public override string _GetCode(Godot.Collections.Array inputVars, Godot.Collections.Array outputVars, Shader.Mode mode, VisualShader.Type type) + public override string _GetCode(Godot.Collections.Array<string> inputVars, Godot.Collections.Array<string> outputVars, Shader.Mode mode, VisualShader.Type type) { return ""; } diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig new file mode 100644 index 0000000000..d4e71b1bd9 --- /dev/null +++ b/modules/mono/glue/GodotSharp/.editorconfig @@ -0,0 +1,8 @@ +[**/Generated/**.cs] +# Validate parameter is non-null before using it +# Useful for generated code, as it disables nullable +dotnet_diagnostic.CA1062.severity = error +# CA1069: Enums should not have duplicate values +dotnet_diagnostic.CA1069.severity = none +# CA1708: Identifiers should differ by more than case +dotnet_diagnostic.CA1708.severity = none diff --git a/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml new file mode 100644 index 0000000000..2dc350d4f2 --- /dev/null +++ b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml @@ -0,0 +1,5 @@ +<assembly name="System.Runtime.InteropServices"> + <member name="T:System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute"> + <attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" /> + </member> +</assembly> diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs new file mode 100644 index 0000000000..686023a077 --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs @@ -0,0 +1,24 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators.Internal; + +internal struct CallbacksData +{ + public CallbacksData(INamedTypeSymbol nativeTypeSymbol, INamedTypeSymbol funcStructSymbol) + { + NativeTypeSymbol = nativeTypeSymbol; + FuncStructSymbol = funcStructSymbol; + Methods = NativeTypeSymbol.GetMembers() + .Where(symbol => symbol is IMethodSymbol { IsPartialDefinition: true }) + .Cast<IMethodSymbol>() + .ToImmutableArray(); + } + + public INamedTypeSymbol NativeTypeSymbol { get; } + + public INamedTypeSymbol FuncStructSymbol { get; } + + public ImmutableArray<IMethodSymbol> Methods { get; } +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs new file mode 100644 index 0000000000..16e96c725a --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs @@ -0,0 +1,65 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators.Internal; + +internal static class Common +{ + public static void ReportNonPartialUnmanagedCallbacksClass( + GeneratorExecutionContext context, + ClassDeclarationSyntax cds, INamedTypeSymbol symbol + ) + { + string message = + "Missing partial modifier on declaration of type '" + + $"{symbol.FullQualifiedName()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; + + string description = $"{message}. Classes with attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' " + + "must be declared with the partial modifier."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-INTERNAL-G0001", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + cds.GetLocation(), + cds.SyntaxTree.FilePath)); + } + + public static void ReportNonPartialUnmanagedCallbacksOuterClass( + GeneratorExecutionContext context, + TypeDeclarationSyntax outerTypeDeclSyntax + ) + { + var outerSymbol = context.Compilation + .GetSemanticModel(outerTypeDeclSyntax.SyntaxTree) + .GetDeclaredSymbol(outerTypeDeclSyntax); + + string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? + namedTypeSymbol.FullQualifiedName() : + "type not found"; + + string message = + $"Missing partial modifier on declaration of type '{fullQualifiedName}', " + + $"which contains one or more subclasses with attribute " + + $"'{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; + + string description = $"{message}. Classes with attribute " + + $"'{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' and their " + + "containing types must be declared with the partial modifier."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-INTERNAL-G0002", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + outerTypeDeclSyntax.GetLocation(), + outerTypeDeclSyntax.SyntaxTree.FilePath)); + } +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs new file mode 100644 index 0000000000..fac362479a --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators.Internal; + +internal static class ExtensionMethods +{ + public static AttributeData? GetGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol) + => symbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGenerateUnmanagedCallbacksAttribute() ?? false); + + private static bool HasGenerateUnmanagedCallbacksAttribute( + this ClassDeclarationSyntax cds, Compilation compilation, + out INamedTypeSymbol? symbol + ) + { + var sm = compilation.GetSemanticModel(cds.SyntaxTree); + + var classTypeSymbol = sm.GetDeclaredSymbol(cds); + if (classTypeSymbol == null) + { + symbol = null; + return false; + } + + if (!classTypeSymbol.GetAttributes() + .Any(a => a.AttributeClass?.IsGenerateUnmanagedCallbacksAttribute() ?? false)) + { + symbol = null; + return false; + } + + symbol = classTypeSymbol; + return true; + } + + private static bool IsGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GeneratorClasses.GenerateUnmanagedCallbacksAttr; + + public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectUnmanagedCallbacksClasses( + this IEnumerable<ClassDeclarationSyntax> source, + Compilation compilation + ) + { + foreach (var cds in source) + { + if (cds.HasGenerateUnmanagedCallbacksAttribute(compilation, out var symbol)) + yield return (cds, symbol!); + } + } + + public static bool IsNested(this TypeDeclarationSyntax cds) + => cds.Parent is TypeDeclarationSyntax; + + public static bool IsPartial(this TypeDeclarationSyntax cds) + => cds.Modifiers.Any(SyntaxKind.PartialKeyword); + + public static bool AreAllOuterTypesPartial( + this TypeDeclarationSyntax cds, + out TypeDeclarationSyntax? typeMissingPartial + ) + { + SyntaxNode? outerSyntaxNode = cds.Parent; + + while (outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax) + { + if (!outerTypeDeclSyntax.IsPartial()) + { + typeMissingPartial = outerTypeDeclSyntax; + return false; + } + + outerSyntaxNode = outerSyntaxNode.Parent; + } + + typeMissingPartial = null; + return true; + } + + public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol) + { + string? keyword = namedTypeSymbol.DeclaringSyntaxReferences + .OfType<TypeDeclarationSyntax>().FirstOrDefault()? + .Keyword.Text; + + return keyword ?? namedTypeSymbol.TypeKind switch + { + TypeKind.Interface => "interface", + TypeKind.Struct => "struct", + _ => "class" + }; + } + + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + public static string FullQualifiedName(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + + public static string NameWithTypeParameters(this INamedTypeSymbol symbol) + { + return symbol.IsGenericType ? + string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") : + symbol.Name; + } + + public static string FullQualifiedName(this INamespaceSymbol symbol) + => symbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) + => qualifiedName + // AddSource() doesn't support angle brackets + .Replace("<", "(Of ") + .Replace(">", ")"); +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/GeneratorClasses.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/GeneratorClasses.cs new file mode 100644 index 0000000000..1bbb33f5a1 --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/GeneratorClasses.cs @@ -0,0 +1,6 @@ +namespace Godot.SourceGenerators.Internal; + +internal static class GeneratorClasses +{ + public const string GenerateUnmanagedCallbacksAttr = "Godot.SourceGenerators.Internal.GenerateUnmanagedCallbacksAttribute"; +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Godot.SourceGenerators.Internal.csproj b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Godot.SourceGenerators.Internal.csproj new file mode 100644 index 0000000000..4d1a5bb76c --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Godot.SourceGenerators.Internal.csproj @@ -0,0 +1,11 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" /> + </ItemGroup> +</Project> diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs new file mode 100644 index 0000000000..da578309bc --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs @@ -0,0 +1,463 @@ +using System.Text; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators.Internal; + +[Generator] +public class UnmanagedCallbacksGenerator : ISourceGenerator +{ + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization(ctx => { GenerateAttribute(ctx); }); + } + + public void Execute(GeneratorExecutionContext context) + { + INamedTypeSymbol[] unmanagedCallbacksClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectUnmanagedCallbacksClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialUnmanagedCallbacksOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialUnmanagedCallbacksClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + foreach (var symbol in unmanagedCallbacksClasses) + { + var attr = symbol.GetGenerateUnmanagedCallbacksAttribute(); + if (attr == null || attr.ConstructorArguments.Length != 1) + { + // TODO: Report error or throw exception, this is an invalid case and should never be reached + System.Diagnostics.Debug.Fail("FAILED!"); + continue; + } + + var funcStructType = (INamedTypeSymbol?)attr.ConstructorArguments[0].Value; + if (funcStructType == null) + { + // TODO: Report error or throw exception, this is an invalid case and should never be reached + System.Diagnostics.Debug.Fail("FAILED!"); + continue; + } + + var data = new CallbacksData(symbol, funcStructType); + GenerateInteropMethodImplementations(context, data); + GenerateUnmanagedCallbacksStruct(context, data); + } + } + + private void GenerateAttribute(GeneratorPostInitializationContext context) + { + string source = @"using System; + +namespace Godot.SourceGenerators.Internal +{ +internal class GenerateUnmanagedCallbacksAttribute : Attribute +{ + public Type FuncStructType { get; } + + public GenerateUnmanagedCallbacksAttribute(Type funcStructType) + { + FuncStructType = funcStructType; + } +} +}"; + + context.AddSource("GenerateUnmanagedCallbacksAttribute.generated", + SourceText.From(source, Encoding.UTF8)); + } + + private void GenerateInteropMethodImplementations(GeneratorExecutionContext context, CallbacksData data) + { + var symbol = data.NativeTypeSymbol; + + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + bool isInnerClass = symbol.ContainingType != null; + + var source = new StringBuilder(); + var methodSource = new StringBuilder(); + var methodCallArguments = new StringBuilder(); + var methodSourceAfterCall = new StringBuilder(); + + source.Append( + @"using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.Bridge; +using Godot.NativeInterop; + +#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores + +"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append("\n{\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); + source.Append($"unsafe partial class {symbol.Name}\n"); + source.Append("{\n"); + source.Append($" private static {data.FuncStructSymbol.FullQualifiedName()} _unmanagedCallbacks;\n\n"); + + foreach (var callback in data.Methods) + { + methodSource.Clear(); + methodCallArguments.Clear(); + methodSourceAfterCall.Clear(); + + source.Append(" [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]\n"); + source.Append($" {SyntaxFacts.GetText(callback.DeclaredAccessibility)} "); + + if (callback.IsStatic) + source.Append("static "); + + source.Append("partial "); + source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(' '); + source.Append(callback.Name); + source.Append('('); + + for (int i = 0; i < callback.Parameters.Length; i++) + { + var parameter = callback.Parameters[i]; + + source.Append(parameter.ToDisplayString()); + source.Append(' '); + source.Append(parameter.Name); + + if (parameter.RefKind == RefKind.Out) + { + // Only assign default if the parameter won't be passed by-ref or copied later. + if (IsGodotInteropStruct(parameter.Type)) + methodSource.Append($" {parameter.Name} = default;\n"); + } + + if (IsByRefParameter(parameter)) + { + if (IsGodotInteropStruct(parameter.Type)) + { + methodSource.Append(" "); + AppendCustomUnsafeAsPointer(methodSource, parameter, out string varName); + methodCallArguments.Append(varName); + } + else if (parameter.Type.IsValueType) + { + methodSource.Append(" "); + AppendCopyToStackAndGetPointer(methodSource, parameter, out string varName); + methodCallArguments.Append($"&{varName}"); + + if (parameter.RefKind is RefKind.Out or RefKind.Ref) + { + methodSourceAfterCall.Append($" {parameter.Name} = {varName};\n"); + } + } + else + { + // If it's a by-ref param and we can't get the pointer + // just pass it by-ref and let it be pinned. + AppendRefKind(methodCallArguments, parameter.RefKind) + .Append(' ') + .Append(parameter.Name); + } + } + else + { + methodCallArguments.Append(parameter.Name); + } + + if (i < callback.Parameters.Length - 1) + { + source.Append(", "); + methodCallArguments.Append(", "); + } + } + + source.Append(")\n"); + source.Append(" {\n"); + + source.Append(methodSource); + source.Append(" "); + + if (!callback.ReturnsVoid) + { + if (methodSourceAfterCall.Length != 0) + source.Append($"{callback.ReturnType.FullQualifiedName()} ret = "); + else + source.Append("return "); + } + + source.Append($"_unmanagedCallbacks.{callback.Name}("); + source.Append(methodCallArguments); + source.Append(");\n"); + + if (methodSourceAfterCall.Length != 0) + { + source.Append(methodSourceAfterCall); + + if (!callback.ReturnsVoid) + source.Append(" return ret;\n"); + } + + source.Append(" }\n\n"); + } + + source.Append("}\n"); + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + source.Append("\n}"); + + source.Append("\n\n#pragma warning restore CA1707\n"); + + context.AddSource($"{data.NativeTypeSymbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private void GenerateUnmanagedCallbacksStruct(GeneratorExecutionContext context, CallbacksData data) + { + var symbol = data.FuncStructSymbol; + + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + bool isInnerClass = symbol.ContainingType != null; + + var source = new StringBuilder(); + + source.Append( + @"using System.Runtime.InteropServices; +using Godot.NativeInterop; + +#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores + +"); + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append("\n{\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("[StructLayout(LayoutKind.Sequential)]\n"); + source.Append($"unsafe partial struct {symbol.Name}\n{{\n"); + + foreach (var callback in data.Methods) + { + source.Append(" "); + source.Append(callback.DeclaredAccessibility == Accessibility.Public ? "public " : "internal "); + + source.Append("delegate* unmanaged<"); + + foreach (var parameter in callback.Parameters) + { + if (IsByRefParameter(parameter)) + { + if (IsGodotInteropStruct(parameter.Type) || parameter.Type.IsValueType) + { + AppendPointerType(source, parameter.Type); + } + else + { + // If it's a by-ref param and we can't get the pointer + // just pass it by-ref and let it be pinned. + AppendRefKind(source, parameter.RefKind) + .Append(' ') + .Append(parameter.Type.FullQualifiedName()); + } + } + else + { + source.Append(parameter.Type.FullQualifiedName()); + } + + source.Append(", "); + } + + source.Append(callback.ReturnType.FullQualifiedName()); + source.Append($"> {callback.Name};\n"); + } + + source.Append("}\n"); + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + source.Append("}\n"); + + source.Append("\n#pragma warning restore CA1707\n"); + + context.AddSource($"{symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static bool IsGodotInteropStruct(ITypeSymbol type) => + GodotInteropStructs.Contains(type.FullQualifiedName()); + + private static bool IsByRefParameter(IParameterSymbol parameter) => + parameter.RefKind is RefKind.In or RefKind.Out or RefKind.Ref; + + private static StringBuilder AppendRefKind(StringBuilder source, RefKind refKind) => + refKind switch + { + RefKind.In => source.Append("in"), + RefKind.Out => source.Append("out"), + RefKind.Ref => source.Append("ref"), + _ => source, + }; + + private static void AppendPointerType(StringBuilder source, ITypeSymbol type) + { + source.Append(type.FullQualifiedName()); + source.Append('*'); + } + + private static void AppendCustomUnsafeAsPointer(StringBuilder source, IParameterSymbol parameter, + out string varName) + { + varName = $"{parameter.Name}_ptr"; + + AppendPointerType(source, parameter.Type); + source.Append(' '); + source.Append(varName); + source.Append(" = "); + + source.Append('('); + AppendPointerType(source, parameter.Type); + source.Append(')'); + + if (parameter.RefKind == RefKind.In) + source.Append("CustomUnsafe.ReadOnlyRefAsPointer(in "); + else + source.Append("CustomUnsafe.AsPointer(ref "); + + source.Append(parameter.Name); + + source.Append(");\n"); + } + + private static void AppendCopyToStackAndGetPointer(StringBuilder source, IParameterSymbol parameter, + out string varName) + { + varName = $"{parameter.Name}_copy"; + + source.Append(parameter.Type.FullQualifiedName()); + source.Append(' '); + source.Append(varName); + if (parameter.RefKind is RefKind.In or RefKind.Ref) + { + source.Append(" = "); + source.Append(parameter.Name); + } + + source.Append(";\n"); + } + + private static readonly string[] GodotInteropStructs = + { + "Godot.NativeInterop.godot_ref", + "Godot.NativeInterop.godot_variant_call_error", + "Godot.NativeInterop.godot_variant", + "Godot.NativeInterop.godot_string", + "Godot.NativeInterop.godot_string_name", + "Godot.NativeInterop.godot_node_path", + "Godot.NativeInterop.godot_signal", + "Godot.NativeInterop.godot_callable", + "Godot.NativeInterop.godot_array", + "Godot.NativeInterop.godot_dictionary", + "Godot.NativeInterop.godot_packed_byte_array", + "Godot.NativeInterop.godot_packed_int32_array", + "Godot.NativeInterop.godot_packed_int64_array", + "Godot.NativeInterop.godot_packed_float32_array", + "Godot.NativeInterop.godot_packed_float64_array", + "Godot.NativeInterop.godot_packed_string_array", + "Godot.NativeInterop.godot_packed_vector2_array", + "Godot.NativeInterop.godot_packed_vector3_array", + "Godot.NativeInterop.godot_packed_color_array", + }; +} diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/GodotPlugins.csproj b/modules/mono/glue/GodotSharp/GodotPlugins/GodotPlugins.csproj new file mode 100644 index 0000000000..e720d3878c --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotPlugins/GodotPlugins.csproj @@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + + <!-- To generate the .runtimeconfig.json file--> + <EnableDynamicLoading>true</EnableDynamicLoading> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\GodotSharp\GodotSharp.csproj" /> + </ItemGroup> + +</Project> diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs new file mode 100644 index 0000000000..dad7464410 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using Godot.Bridge; +using Godot.NativeInterop; + +namespace GodotPlugins +{ + public static class Main + { + // IMPORTANT: + // Keeping strong references to the AssemblyLoadContext (our PluginLoadContext) prevents + // it from being unloaded. To avoid issues, we wrap the reference in this class, and mark + // all the methods that access it as non-inlineable. This way we prevent local references + // (either real or introduced by the JIT) to escape the scope of these methods due to + // inlining, which could keep the AssemblyLoadContext alive while trying to unload. + private sealed class PluginLoadContextWrapper + { + private PluginLoadContext? _pluginLoadContext; + + public string? AssemblyLoadedPath + { + [MethodImpl(MethodImplOptions.NoInlining)] + get => _pluginLoadContext?.AssemblyLoadedPath; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName( + AssemblyName assemblyName, + string pluginPath, + ICollection<string> sharedAssemblies, + AssemblyLoadContext mainLoadContext + ) + { + var wrapper = new PluginLoadContextWrapper(); + wrapper._pluginLoadContext = new PluginLoadContext( + pluginPath, sharedAssemblies, mainLoadContext); + var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName); + return (assembly, wrapper); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public WeakReference CreateWeakReference() + { + return new WeakReference(_pluginLoadContext, trackResurrection: true); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal void Unload() + { + _pluginLoadContext?.Unload(); + _pluginLoadContext = null; + } + } + + private static readonly List<AssemblyName> SharedAssemblies = new(); + private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly; + private static Assembly? _editorApiAssembly; + private static PluginLoadContextWrapper? _projectLoadContext; + + private static readonly AssemblyLoadContext MainLoadContext = + AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? + AssemblyLoadContext.Default; + + private static DllImportResolver? _dllImportResolver; + + // Right now we do it this way for simplicity as hot-reload is disabled. It will need to be changed later. + [UnmanagedCallersOnly] + // ReSharper disable once UnusedMember.Local + private static unsafe godot_bool InitializeFromEngine(IntPtr godotDllHandle, godot_bool editorHint, + PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks, + IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + try + { + _dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport; + + SharedAssemblies.Add(CoreApiAssembly.GetName()); + NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver); + + AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool()); + NativeFuncs.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); + + if (editorHint.ToBool()) + { + _editorApiAssembly = Assembly.Load("GodotSharpEditor"); + SharedAssemblies.Add(_editorApiAssembly.GetName()); + NativeLibrary.SetDllImportResolver(_editorApiAssembly, _dllImportResolver); + } + + *pluginsCallbacks = new() + { + LoadProjectAssemblyCallback = &LoadProjectAssembly, + LoadToolsAssemblyCallback = &LoadToolsAssembly, + UnloadProjectPluginCallback = &UnloadProjectPlugin, + }; + + *managedCallbacks = ManagedCallbacks.Create(); + + return godot_bool.True; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return godot_bool.False; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct PluginsCallbacks + { + public unsafe delegate* unmanaged<char*, godot_string*, godot_bool> LoadProjectAssemblyCallback; + public unsafe delegate* unmanaged<char*, IntPtr, int, IntPtr> LoadToolsAssemblyCallback; + public unsafe delegate* unmanaged<godot_bool> UnloadProjectPluginCallback; + } + + [UnmanagedCallersOnly] + private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath) + { + try + { + if (_projectLoadContext != null) + return godot_bool.True; // Already loaded + + string assemblyPath = new(nAssemblyPath); + + (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath); + + string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath; + *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath); + + ScriptManagerBridge.LookupScriptsInAssembly(projectAssembly); + + return godot_bool.True; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + private static unsafe IntPtr LoadToolsAssembly(char* nAssemblyPath, + IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + try + { + string assemblyPath = new(nAssemblyPath); + + if (_editorApiAssembly == null) + throw new InvalidOperationException("The Godot editor API assembly is not loaded"); + + var (assembly, _) = LoadPlugin(assemblyPath); + + NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!); + + var method = assembly.GetType("GodotTools.GodotSharpEditor")? + .GetMethod("InternalCreateInstance", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + if (method == null) + { + throw new MissingMethodException("GodotTools.GodotSharpEditor", + "InternalCreateInstance"); + } + + return (IntPtr?)method + .Invoke(null, new object[] { unmanagedCallbacks, unmanagedCallbacksSize }) + ?? IntPtr.Zero; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return IntPtr.Zero; + } + } + + private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath) + { + string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); + + var sharedAssemblies = new List<string>(); + + foreach (var sharedAssembly in SharedAssemblies) + { + string? sharedAssemblyName = sharedAssembly.Name; + if (sharedAssemblyName != null) + sharedAssemblies.Add(sharedAssemblyName); + } + + return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName( + new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext); + } + + [UnmanagedCallersOnly] + private static godot_bool UnloadProjectPlugin() + { + try + { + return UnloadPlugin(ref _projectLoadContext).ToGodotBool(); + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return godot_bool.False; + } + } + + private static bool UnloadPlugin(ref PluginLoadContextWrapper? pluginLoadContext) + { + try + { + if (pluginLoadContext == null) + return true; + + Console.WriteLine("Unloading assembly load context..."); + + var alcWeakReference = pluginLoadContext.CreateWeakReference(); + + pluginLoadContext.Unload(); + pluginLoadContext = null; + + int startTimeMs = Environment.TickCount; + bool takingTooLong = false; + + while (alcWeakReference.IsAlive) + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); + GC.WaitForPendingFinalizers(); + + if (!alcWeakReference.IsAlive) + break; + + int elapsedTimeMs = Environment.TickCount - startTimeMs; + + if (!takingTooLong && elapsedTimeMs >= 2000) + { + takingTooLong = true; + + // TODO: How to log from GodotPlugins? (delegate pointer?) + Console.Error.WriteLine("Assembly unloading is taking longer than expected..."); + } + else if (elapsedTimeMs >= 5000) + { + // TODO: How to log from GodotPlugins? (delegate pointer?) + Console.Error.WriteLine( + "Failed to unload assemblies. Possible causes: Strong GC handles, running threads, etc."); + + return false; + } + } + + Console.WriteLine("Assembly load context unloaded successfully."); + + return true; + } + catch (Exception e) + { + // TODO: How to log exceptions from GodotPlugins? (delegate pointer?) + Console.Error.WriteLine(e); + return false; + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs new file mode 100644 index 0000000000..dcd572c65e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; + +namespace GodotPlugins +{ + public class PluginLoadContext : AssemblyLoadContext + { + private readonly AssemblyDependencyResolver _resolver; + private readonly ICollection<string> _sharedAssemblies; + private readonly AssemblyLoadContext _mainLoadContext; + + public string? AssemblyLoadedPath { get; private set; } + + public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies, + AssemblyLoadContext mainLoadContext) + : base(isCollectible: true) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + _sharedAssemblies = sharedAssemblies; + _mainLoadContext = mainLoadContext; + } + + protected override Assembly? Load(AssemblyName assemblyName) + { + if (assemblyName.Name == null) + return null; + + if (_sharedAssemblies.Contains(assemblyName.Name)) + return _mainLoadContext.LoadFromAssemblyName(assemblyName); + + string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath != null) + { + AssemblyLoadedPath = assemblyPath; + + // Load in memory to prevent locking the file + using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read); + string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb"); + + if (File.Exists(pdbPath)) + { + using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return LoadFromStream(assemblyFile, pdbFile); + } + + return LoadFromStream(assemblyFile); + } + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (libraryPath != null) + return LoadUnmanagedDllFromPath(libraryPath); + + return IntPtr.Zero; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln b/modules/mono/glue/GodotSharp/GodotSharp.sln index 4896d0a07d..8db42c2d1a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp.sln +++ b/modules/mono/glue/GodotSharp/GodotSharp.sln @@ -4,6 +4,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "GodotSharp\Go EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpEditor", "GodotSharpEditor\GodotSharpEditor.csproj", "{8FBEC238-D944-4074-8548-B3B524305905}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotPlugins", "GodotPlugins\GodotPlugins.csproj", "{944B77DB-497B-47F5-A1E3-81C35E3E9D4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Internal", "Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj", "{7749662B-E30C-419A-B745-13852573360A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +22,13 @@ Global {8FBEC238-D944-4074-8548-B3B524305905}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FBEC238-D944-4074-8548-B3B524305905}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FBEC238-D944-4074-8548-B3B524305905}.Release|Any CPU.Build.0 = Release|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Release|Any CPU.Build.0 = Release|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings new file mode 100644 index 0000000000..ba65b61e95 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings @@ -0,0 +1,8 @@ +<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String> + <s:Boolean x:Key="/Default/UserDictionary/Words/=alcs/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=gdnative/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=godotsharp/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=icall/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=quat/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=vcall/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs index 850ae7fc3b..f87f37bc43 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index a412047196..f2984bd1fb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -1,47 +1,35 @@ using System; using System.Collections.Generic; using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot.Collections { - internal class ArraySafeHandle : SafeHandle - { - public ArraySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) - { - this.handle = handle; - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero; } - } - - protected override bool ReleaseHandle() - { - Array.godot_icall_Array_Dtor(handle); - return true; - } - } - /// <summary> /// Wrapper around Godot's Array class, an array of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. Otherwise prefer .NET collections /// such as <see cref="System.Array"/> or <see cref="List{T}"/>. /// </summary> - public class Array : IList, IDisposable + public sealed class Array : + IList<Variant>, + IReadOnlyList<Variant>, + ICollection, + IDisposable { - private ArraySafeHandle _safeHandle; - private bool _disposed = false; + internal godot_array.movable NativeValue; + + private WeakReference<IDisposable> _weakReferenceToSelf; /// <summary> /// Constructs a new empty <see cref="Array"/>. /// </summary> public Array() { - _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } /// <summary> @@ -49,12 +37,12 @@ namespace Godot.Collections /// </summary> /// <param name="collection">The collection of elements to construct from.</param> /// <returns>A new Godot Array.</returns> - public Array(IEnumerable collection) : this() + public Array(IEnumerable<Variant> collection) : this() { if (collection == null) - throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); + throw new ArgumentNullException(nameof(collection)); - foreach (object element in collection) + foreach (Variant element in collection) Add(element); } @@ -63,31 +51,126 @@ namespace Godot.Collections /// </summary> /// <param name="array">The objects to put in the new array.</param> /// <returns>A new Godot Array.</returns> - public Array(params object[] array) : this() + public Array(Variant[] array) : this() { if (array == null) - { - throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); - } - _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array)); + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; } - internal Array(ArraySafeHandle handle) + public Array(Span<StringName> array) : this() { - _safeHandle = handle; + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; } - internal Array(IntPtr handle) + public Array(Span<NodePath> array) : this() { - _safeHandle = new ArraySafeHandle(handle); + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + public Array(Span<RID> array) : this() + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + // We must use ReadOnlySpan instead of Span here as this can accept implicit conversions + // from derived types (e.g.: Node[]). Implicit conversion from Derived[] to Base[] are + // fine as long as the array is not mutated. However, Span does this type checking at + // instantiation, so it's not possible to use it even when not mutating anything. + // ReSharper disable once RedundantNameQualifier + public Array(ReadOnlySpan<Godot.Object> array) : this() + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; } - internal IntPtr GetPtr() + private Array(godot_array nativeValueToOwn) { - if (_disposed) - throw new ObjectDisposedException(GetType().FullName); + NativeValue = (godot_array.movable)(nativeValueToOwn.IsAllocated ? + nativeValueToOwn : + NativeFuncs.godotsharp_array_new()); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } + + // Explicit name to make it very clear + internal static Array CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn) + => new Array(nativeValueToOwn); - return _safeHandle.DangerousGetHandle(); + ~Array() + { + Dispose(false); + } + + /// <summary> + /// Disposes of this <see cref="Array"/>. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) + { + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); + } } /// <summary> @@ -97,7 +180,10 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array Duplicate(bool deep = false) { - return new Array(godot_icall_Array_Duplicate(GetPtr(), deep)); + godot_array newArray; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_duplicate(ref self, deep.ToGodotBool(), out newArray); + return CreateTakingOwnershipOfDisposableValue(newArray); } /// <summary> @@ -107,7 +193,8 @@ namespace Godot.Collections /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> public Error Resize(int newSize) { - return godot_icall_Array_Resize(GetPtr(), newSize); + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_resize(ref self, newSize); } /// <summary> @@ -115,7 +202,8 @@ namespace Godot.Collections /// </summary> public void Shuffle() { - godot_icall_Array_Shuffle(GetPtr()); + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_shuffle(ref self); } /// <summary> @@ -126,94 +214,136 @@ namespace Godot.Collections /// <returns>A new Godot Array with the contents of both arrays.</returns> public static Array operator +(Array left, Array right) { - return new Array(godot_icall_Array_Concatenate(left.GetPtr(), right.GetPtr())); - } - - // IDisposable - - /// <summary> - /// Disposes of this <see cref="Array"/>. - /// </summary> - public void Dispose() - { - if (_disposed) - return; - - if (_safeHandle != null) + if (left == null) { - _safeHandle.Dispose(); - _safeHandle = null; + if (right == null) + return new Array(); + + return right.Duplicate(deep: false); } - _disposed = true; - } + if (right == null) + return left.Duplicate(deep: false); + + int leftCount = left.Count; + int rightCount = right.Count; - // IList + Array newArray = left.Duplicate(deep: false); + newArray.Resize(leftCount + rightCount); - bool IList.IsReadOnly => false; + for (int i = 0; i < rightCount; i++) + newArray[i + leftCount] = right[i]; - bool IList.IsFixedSize => false; + return newArray; + } /// <summary> - /// Returns the object at the given <paramref name="index"/>. + /// Returns the item at the given <paramref name="index"/>. /// </summary> - /// <value>The object at the given <paramref name="index"/>.</value> - public object this[int index] + /// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value> + public unsafe Variant this[int index] { - get => godot_icall_Array_At(GetPtr(), index); - set => godot_icall_Array_SetAt(GetPtr(), index, value); + get + { + GetVariantBorrowElementAt(index, out godot_variant borrowElem); + return Variant.CreateCopyingBorrowed(borrowElem); + } + set + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + var self = (godot_array)NativeValue; + godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); + godot_variant* itemPtr = &ptrw[index]; + (*itemPtr).Dispose(); + *itemPtr = value.CopyNativeVariant(); + } } /// <summary> - /// Adds an object to the end of this <see cref="Array"/>. + /// Adds an item to the end of this <see cref="Array"/>. /// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// </summary> - /// <param name="value">The object to add.</param> - /// <returns>The new size after adding the object.</returns> - public int Add(object value) => godot_icall_Array_Add(GetPtr(), value); + /// <param name="item">The <see cref="Variant"/> item to add.</param> + public void Add(Variant item) + { + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); + } /// <summary> - /// Checks if this <see cref="Array"/> contains the given object. + /// Checks if this <see cref="Array"/> contains the given item. /// </summary> - /// <param name="value">The item to look for.</param> - /// <returns>Whether or not this array contains the given object.</returns> - public bool Contains(object value) => godot_icall_Array_Contains(GetPtr(), value); + /// <param name="item">The <see cref="Variant"/> item to look for.</param> + /// <returns>Whether or not this array contains the given item.</returns> + public bool Contains(Variant item) => IndexOf(item) != -1; /// <summary> /// Erases all items from this <see cref="Array"/>. /// </summary> - public void Clear() => godot_icall_Array_Clear(GetPtr()); + public void Clear() => Resize(0); /// <summary> - /// Searches this <see cref="Array"/> for an object + /// Searches this <see cref="Array"/> for an item /// and returns its index or -1 if not found. /// </summary> - /// <param name="value">The object to search for.</param> - /// <returns>The index of the object, or -1 if not found.</returns> - public int IndexOf(object value) => godot_icall_Array_IndexOf(GetPtr(), value); + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int IndexOf(Variant item) + { + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); + } /// <summary> - /// Inserts a new object at a given position in the array. + /// Inserts a new item at a given position in the array. /// The position must be a valid position of an existing item, /// or the position at the end of the array. /// Existing items will be moved to the right. /// </summary> /// <param name="index">The index to insert at.</param> - /// <param name="value">The object to insert.</param> - public void Insert(int index, object value) => godot_icall_Array_Insert(GetPtr(), index, value); + /// <param name="item">The <see cref="Variant"/> item to insert.</param> + public void Insert(int index, Variant item) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); + } /// <summary> - /// Removes the first occurrence of the specified <paramref name="value"/> + /// Removes the first occurrence of the specified <paramref name="item"/> /// from this <see cref="Array"/>. /// </summary> - /// <param name="value">The value to remove.</param> - public void Remove(object value) => godot_icall_Array_Remove(GetPtr(), value); + /// <param name="item">The value to remove.</param> + public bool Remove(Variant item) + { + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } /// <summary> /// Removes an element from this <see cref="Array"/> by index. /// </summary> /// <param name="index">The index of the element to remove.</param> - public void RemoveAt(int index) => godot_icall_Array_RemoveAt(GetPtr(), index); + public void RemoveAt(int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_remove_at(ref self, index); + } // ICollection @@ -222,28 +352,77 @@ namespace Godot.Collections /// This is also known as the size or length of the array. /// </summary> /// <returns>The number of elements.</returns> - public int Count => godot_icall_Array_Count(GetPtr()); - - object ICollection.SyncRoot => this; + public int Count => NativeValue.DangerousSelfRef.Size; bool ICollection.IsSynchronized => false; + object ICollection.SyncRoot => false; + + bool ICollection<Variant>.IsReadOnly => false; + /// <summary> /// Copies the elements of this <see cref="Array"/> to the given - /// untyped C# array, starting at the given index. + /// <see cref="Variant"/> C# array, starting at the given index. /// </summary> /// <param name="array">The array to copy to.</param> - /// <param name="index">The index to start at.</param> - public void CopyTo(System.Array array, int index) + /// <param name="arrayIndex">The index to start at.</param> + public void CopyTo(Variant[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + } + + int count = Count; + + if (array.Length < (arrayIndex + count)) + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } + + unsafe + { + for (int i = 0; i < count; i++) + { + array[arrayIndex] = Variant.CreateCopyingBorrowed(NativeValue.DangerousSelfRef.Elements[i]); + arrayIndex++; + } + } + } + + void ICollection.CopyTo(System.Array array, int index) { if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension."); + { + throw new ArgumentOutOfRangeException(nameof(index), + "Number was less than the array's lower bound in the first dimension."); + } + + int count = Count; - // Internal call may throw ArgumentException - godot_icall_Array_CopyTo(GetPtr(), array, index); + if (array.Length < (index + count)) + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } + + unsafe + { + for (int i = 0; i < count; i++) + { + object obj = Marshaling.ConvertVariantToManagedObject(NativeValue.DangerousSelfRef.Elements[i]); + array.SetValue(obj, index); + index++; + } + } } // IEnumerable @@ -252,7 +431,7 @@ namespace Godot.Collections /// Gets an enumerator for this <see cref="Array"/>. /// </summary> /// <returns>An enumerator.</returns> - public IEnumerator GetEnumerator() + public IEnumerator<Variant> GetEnumerator() { int count = Count; @@ -262,77 +441,37 @@ namespace Godot.Collections } } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// <summary> /// Converts this <see cref="Array"/> to a string. /// </summary> /// <returns>A string representation of this array.</returns> public override string ToString() { - return godot_icall_Array_ToString(GetPtr()); + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_to_string(ref self, out godot_string str); + using (str) + return Marshaling.ConvertStringToManaged(str); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Ctor(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Array_At(IntPtr ptr, int index); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Array_Count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Array_Add(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Clear(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Array_Contains(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Array_IndexOf(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Insert(IntPtr ptr, int index, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Array_Remove(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_RemoveAt(IntPtr ptr, int index); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Error godot_icall_Array_Resize(IntPtr ptr, int newSize); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Error godot_icall_Array_Shuffle(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass); + /// <summary> + /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed. + /// </summary> + internal void GetVariantBorrowElementAt(int index, out godot_variant elem) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + GetVariantBorrowElementAtUnchecked(index, out elem); + } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_Array_ToString(IntPtr ptr); + /// <summary> + /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed. + /// </summary> + internal unsafe void GetVariantBorrowElementAtUnchecked(int index, out godot_variant elem) + { + elem = NativeValue.DangerousSelfRef.Elements[index]; + } } /// <summary> @@ -342,16 +481,45 @@ namespace Godot.Collections /// such as arrays or <see cref="List{T}"/>. /// </summary> /// <typeparam name="T">The type of the array.</typeparam> - public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T> + [SuppressMessage("ReSharper", "RedundantExtendsListEntry")] + [SuppressMessage("Naming", "CA1710", MessageId = "Identifiers should have correct suffix")] + public sealed class Array<[MustBeVariant] T> : + IList<T>, + IReadOnlyList<T>, + ICollection<T>, + IEnumerable<T> { - private Array _objectArray; + // ReSharper disable StaticMemberInGenericType + // Warning is about unique static fields being created for each generic type combination: + // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html + // In our case this is exactly what we want. - internal static int elemTypeEncoding; - internal static IntPtr elemTypeClass; + private static unsafe delegate* managed<in T, godot_variant> _convertToVariantCallback; + private static unsafe delegate* managed<in godot_variant, T> _convertToManagedCallback; - static Array() + // ReSharper restore StaticMemberInGenericType + + static unsafe Array() { - Array.godot_icall_Array_Generic_GetElementTypeInfo(typeof(T), out elemTypeEncoding, out elemTypeClass); + _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); + _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); + } + + private static unsafe void ValidateVariantConversionCallbacks() + { + if (_convertToVariantCallback == null || _convertToManagedCallback == null) + { + throw new InvalidOperationException( + $"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'"); + } + } + + private readonly Array _underlyingArray; + + internal ref godot_array.movable NativeValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _underlyingArray.NativeValue; } /// <summary> @@ -359,7 +527,9 @@ namespace Godot.Collections /// </summary> public Array() { - _objectArray = new Array(); + ValidateVariantConversionCallbacks(); + + _underlyingArray = new Array(); } /// <summary> @@ -369,10 +539,15 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array(IEnumerable<T> collection) { + ValidateVariantConversionCallbacks(); + if (collection == null) - throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); + throw new ArgumentNullException(nameof(collection)); + + _underlyingArray = new Array(); - _objectArray = new Array(collection); + foreach (T element in collection) + Add(element); } /// <summary> @@ -380,13 +555,17 @@ namespace Godot.Collections /// </summary> /// <param name="array">The items to put in the new array.</param> /// <returns>A new Godot Array.</returns> - public Array(params T[] array) : this() + public Array(T[] array) : this() { + ValidateVariantConversionCallbacks(); + if (array == null) - { - throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); - } - _objectArray = new Array(array); + throw new ArgumentNullException(nameof(array)); + + _underlyingArray = new Array(); + + foreach (T element in array) + Add(element); } /// <summary> @@ -395,23 +574,14 @@ namespace Godot.Collections /// <param name="array">The untyped array to construct from.</param> public Array(Array array) { - _objectArray = array; - } + ValidateVariantConversionCallbacks(); - internal Array(IntPtr handle) - { - _objectArray = new Array(handle); + _underlyingArray = array; } - internal Array(ArraySafeHandle handle) - { - _objectArray = new Array(handle); - } - - internal IntPtr GetPtr() - { - return _objectArray.GetPtr(); - } + // Explicit name to make it very clear + internal static Array<T> CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn) + => new Array<T>(Array.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); /// <summary> /// Converts this typed <see cref="Array{T}"/> to an untyped <see cref="Array"/>. @@ -419,7 +589,7 @@ namespace Godot.Collections /// <param name="from">The typed array to convert.</param> public static explicit operator Array(Array<T> from) { - return from._objectArray; + return from?._underlyingArray; } /// <summary> @@ -429,7 +599,7 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array<T> Duplicate(bool deep = false) { - return new Array<T>(_objectArray.Duplicate(deep)); + return new Array<T>(_underlyingArray.Duplicate(deep)); } /// <summary> @@ -439,7 +609,7 @@ namespace Godot.Collections /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> public Error Resize(int newSize) { - return _objectArray.Resize(newSize); + return _underlyingArray.Resize(newSize); } /// <summary> @@ -447,7 +617,7 @@ namespace Godot.Collections /// </summary> public void Shuffle() { - _objectArray.Shuffle(); + _underlyingArray.Shuffle(); } /// <summary> @@ -458,7 +628,18 @@ namespace Godot.Collections /// <returns>A new Godot Array with the contents of both arrays.</returns> public static Array<T> operator +(Array<T> left, Array<T> right) { - return new Array<T>(left._objectArray + right._objectArray); + if (left == null) + { + if (right == null) + return new Array<T>(); + + return right.Duplicate(deep: false); + } + + if (right == null) + return left.Duplicate(deep: false); + + return new Array<T>(left._underlyingArray + right._underlyingArray); } // IList<T> @@ -467,10 +648,23 @@ namespace Godot.Collections /// Returns the value at the given <paramref name="index"/>. /// </summary> /// <value>The value at the given <paramref name="index"/>.</value> - public T this[int index] + public unsafe T this[int index] { - get { return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass); } - set { _objectArray[index] = value; } + get + { + _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); + return _convertToManagedCallback(borrowElem); + } + set + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + var self = (godot_array)_underlyingArray.NativeValue; + godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); + godot_variant* itemPtr = &ptrw[index]; + (*itemPtr).Dispose(); + *itemPtr = _convertToVariantCallback(value); + } } /// <summary> @@ -479,9 +673,11 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> - public int IndexOf(T item) + public unsafe int IndexOf(T item) { - return _objectArray.IndexOf(item); + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } /// <summary> @@ -492,9 +688,14 @@ namespace Godot.Collections /// </summary> /// <param name="index">The index to insert at.</param> /// <param name="item">The item to insert.</param> - public void Insert(int index, T item) + public unsafe void Insert(int index, T item) { - _objectArray.Insert(index, item); + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); } /// <summary> @@ -503,7 +704,7 @@ namespace Godot.Collections /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { - _objectArray.RemoveAt(index); + _underlyingArray.RemoveAt(index); } // ICollection<T> @@ -513,10 +714,7 @@ namespace Godot.Collections /// This is also known as the size or length of the array. /// </summary> /// <returns>The number of elements.</returns> - public int Count - { - get { return _objectArray.Count; } - } + public int Count => _underlyingArray.Count; bool ICollection<T>.IsReadOnly => false; @@ -526,9 +724,11 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to add.</param> /// <returns>The new size after adding the item.</returns> - public void Add(T item) + public unsafe void Add(T item) { - _objectArray.Add(item); + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); } /// <summary> @@ -536,7 +736,7 @@ namespace Godot.Collections /// </summary> public void Clear() { - _objectArray.Clear(); + _underlyingArray.Clear(); } /// <summary> @@ -544,10 +744,7 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to look for.</param> /// <returns>Whether or not this array contains the given item.</returns> - public bool Contains(T item) - { - return _objectArray.Contains(item); - } + public bool Contains(T item) => IndexOf(item) != -1; /// <summary> /// Copies the elements of this <see cref="Array{T}"/> to the given @@ -561,19 +758,22 @@ namespace Godot.Collections throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); - - // TODO This may be quite slow because every element access is an internal call. - // It could be moved entirely to an internal call if we find out how to do the cast there. + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + } - int count = _objectArray.Count; + int count = Count; if (array.Length < (arrayIndex + count)) - throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } for (int i = 0; i < count; i++) { - array[arrayIndex] = (T)this[i]; + array[arrayIndex] = this[i]; arrayIndex++; } } @@ -586,7 +786,14 @@ namespace Godot.Collections /// <returns>A <see langword="bool"/> indicating success or failure.</returns> public bool Remove(T item) { - return Array.godot_icall_Array_Remove(GetPtr(), item); + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; } // IEnumerable<T> @@ -597,23 +804,26 @@ namespace Godot.Collections /// <returns>An enumerator.</returns> public IEnumerator<T> GetEnumerator() { - int count = _objectArray.Count; + int count = _underlyingArray.Count; for (int i = 0; i < count; i++) { - yield return (T)this[i]; + yield return this[i]; } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// <summary> /// Converts this <see cref="Array{T}"/> to a string. /// </summary> /// <returns>A string representation of this array.</returns> - public override string ToString() => _objectArray.ToString(); + public override string ToString() => _underlyingArray.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Array<T> from) => Variant.CreateFrom(from); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Array<T>(Variant from) => from.AsGodotArray<T>(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs index 2febf37f05..b7d633517a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Godot { /// <summary> @@ -8,25 +10,28 @@ namespace Godot [AttributeUsage(AttributeTargets.Assembly)] public class AssemblyHasScriptsAttribute : Attribute { - private readonly bool requiresLookup; - private readonly System.Type[] scriptTypes; + public bool RequiresLookup { get; } + public Type[]? ScriptTypes { get; } /// <summary> /// Constructs a new AssemblyHasScriptsAttribute instance. /// </summary> public AssemblyHasScriptsAttribute() { - requiresLookup = true; + RequiresLookup = true; + ScriptTypes = null; } /// <summary> /// Constructs a new AssemblyHasScriptsAttribute instance. /// </summary> /// <param name="scriptTypes">The specified type(s) of scripts.</param> - public AssemblyHasScriptsAttribute(System.Type[] scriptTypes) + public AssemblyHasScriptsAttribute(Type[] scriptTypes) { - requiresLookup = false; - this.scriptTypes = scriptTypes; + RequiresLookup = false; + ScriptTypes = scriptTypes; } } } + +#nullable restore diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs deleted file mode 100644 index 0b00878e8c..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// An attribute that disables Godot Generators. - /// </summary> - [AttributeUsage(AttributeTargets.Class)] - public class DisableGodotGeneratorsAttribute : Attribute { } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs index 46eb128d37..3d204bdf9f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs @@ -6,7 +6,7 @@ namespace Godot /// An attribute used to export objects. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public class ExportAttribute : Attribute + public sealed class ExportAttribute : Attribute { private PropertyHint hint; private string hintString; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportCategoryAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportCategoryAttribute.cs new file mode 100644 index 0000000000..101e56f8d3 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportCategoryAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Godot +{ + /// <summary> + /// Define a new category for the following exported properties. This helps to organize properties in the Inspector dock. + /// </summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ExportCategoryAttribute : Attribute + { + private string name; + + /// <summary> + /// Define a new category for the following exported properties. + /// </summary> + /// <param name="name">The name of the category.</param> + public ExportCategoryAttribute(string name) + { + this.name = name; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportGroupAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportGroupAttribute.cs new file mode 100644 index 0000000000..3bd532cec1 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportGroupAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Godot +{ + /// <summary> + /// Define a new group for the following exported properties. This helps to organize properties in the Inspector dock. + /// </summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ExportGroupAttribute : Attribute + { + private string name; + private string prefix; + + /// <summary> + /// Define a new group for the following exported properties. + /// </summary> + /// <param name="name">The name of the group.</param> + /// <param name="prefix">If provided, the group would make group to only consider properties that have this prefix.</param> + public ExportGroupAttribute(string name, string prefix = "") + { + this.name = name; + this.prefix = prefix; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportSubgroupAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportSubgroupAttribute.cs new file mode 100644 index 0000000000..2ae6eb0b68 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportSubgroupAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Godot +{ + /// <summary> + /// Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock. + /// </summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ExportSubgroupAttribute : Attribute + { + private string name; + private string prefix; + + /// <summary> + /// Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock. + /// </summary> + /// <param name="name">The name of the subgroup.</param> + /// <param name="prefix">If provided, the subgroup would make group to only consider properties that have this prefix.</param> + public ExportSubgroupAttribute(string name, string prefix = "") + { + this.name = name; + this.prefix = prefix; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs deleted file mode 100644 index 8d4ff0fdb7..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// An attribute for a method. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - internal class GodotMethodAttribute : Attribute - { - private string methodName; - - public string MethodName { get { return methodName; } } - - /// <summary> - /// Constructs a new GodotMethodAttribute instance. - /// </summary> - /// <param name="methodName">The name of the method.</param> - public GodotMethodAttribute(string methodName) - { - this.methodName = methodName; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/MustBeVariantAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/MustBeVariantAttribute.cs new file mode 100644 index 0000000000..23088378d1 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/MustBeVariantAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Godot +{ + /// <summary> + /// Attribute that restricts generic type parameters to be only types + /// that can be marshaled from/to a <see cref="Variant"/>. + /// </summary> + [AttributeUsage(AttributeTargets.GenericParameter)] + public class MustBeVariantAttribute : Attribute { } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs index 3ebb6612de..2c8a53ae1c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs @@ -8,7 +8,7 @@ namespace Godot [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class ScriptPathAttribute : Attribute { - private string path; + public string Path { get; } /// <summary> /// Constructs a new ScriptPathAttribute instance. @@ -16,7 +16,7 @@ namespace Godot /// <param name="path">The file path to the script</param> public ScriptPathAttribute(string path) { - this.path = path; + Path = path; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 437878818c..4cb9bf5758 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -167,7 +162,7 @@ namespace Godot case 2: return Column2; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } set @@ -184,7 +179,7 @@ namespace Godot Column2 = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } } @@ -386,7 +381,7 @@ namespace Godot case 2: return Row2; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } @@ -413,7 +408,7 @@ namespace Godot Row2 = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } @@ -623,41 +618,6 @@ namespace Godot return tr; } - /// <summary> - /// Returns a vector transformed (multiplied) by the basis matrix. - /// </summary> - /// <seealso cref="XformInv(Vector3)"/> - /// <param name="v">A vector to transform.</param> - /// <returns>The transformed vector.</returns> - public Vector3 Xform(Vector3 v) - { - return new Vector3 - ( - Row0.Dot(v), - Row1.Dot(v), - Row2.Dot(v) - ); - } - - /// <summary> - /// Returns a vector transformed (multiplied) by the transposed basis matrix. - /// - /// Note: This results in a multiplication by the inverse of the - /// basis matrix only if it represents a rotation-reflection. - /// </summary> - /// <seealso cref="Xform(Vector3)"/> - /// <param name="v">A vector to inversely transform.</param> - /// <returns>The inversely transformed vector.</returns> - public Vector3 XformInv(Vector3 v) - { - return new Vector3 - ( - Row0[0] * v.x + Row1[0] * v.y + Row2[0] * v.z, - Row0[1] * v.x + Row1[1] * v.y + Row2[1] * v.z, - Row0[2] * v.x + Row1[2] * v.y + Row2[2] * v.z - ); - } - private static readonly Basis[] _orthoBases = { new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), @@ -862,6 +822,41 @@ namespace Godot } /// <summary> + /// Returns a Vector3 transformed (multiplied) by the basis matrix. + /// </summary> + /// <param name="basis">The basis matrix transformation to apply.</param> + /// <param name="vector">A Vector3 to transform.</param> + /// <returns>The transformed Vector3.</returns> + public static Vector3 operator *(Basis basis, Vector3 vector) + { + return new Vector3 + ( + basis.Row0.Dot(vector), + basis.Row1.Dot(vector), + basis.Row2.Dot(vector) + ); + } + + /// <summary> + /// Returns a Vector3 transformed (multiplied) by the transposed basis matrix. + /// + /// Note: This results in a multiplication by the inverse of the + /// basis matrix only if it represents a rotation-reflection. + /// </summary> + /// <param name="vector">A Vector3 to inversely transform.</param> + /// <param name="basis">The basis matrix transformation to apply.</param> + /// <returns>The inversely transformed vector.</returns> + public static Vector3 operator *(Vector3 vector, Basis basis) + { + return new Vector3 + ( + basis.Row0[0] * vector.x + basis.Row1[0] * vector.y + basis.Row2[0] * vector.z, + basis.Row0[1] * vector.x + basis.Row1[1] * vector.y + basis.Row2[1] * vector.z, + basis.Row0[2] * vector.x + basis.Row1[2] * vector.y + basis.Row2[2] * vector.z + ); + } + + /// <summary> /// Returns <see langword="true"/> if the basis matrices are exactly /// equal. Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs new file mode 100644 index 0000000000..ac2e2fae3c --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs @@ -0,0 +1,18 @@ +namespace Godot.Bridge; + +public static class AlcReloadCfg +{ + private static bool _configured = false; + + public static void Configure(bool alcReloadEnabled) + { + if (_configured) + return; + + _configured = true; + + IsAlcReloadingEnabled = alcReloadEnabled; + } + + internal static bool IsAlcReloadingEnabled = false; +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs new file mode 100644 index 0000000000..ae44f8f4ba --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs @@ -0,0 +1,253 @@ +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + internal static class CSharpInstanceBridge + { + [UnmanagedCallersOnly] + internal static unsafe godot_bool Call(IntPtr godotObjectGCHandle, godot_string_name* method, + godot_variant** args, int argCount, godot_variant_call_error* refCallError, godot_variant* ret) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + { + *ret = default; + (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL; + return godot_bool.False; + } + + bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), + new NativeVariantPtrArgs(args), + argCount, out godot_variant retValue); + + if (!methodInvoked) + { + *ret = default; + // This is important, as it tells Object::call that no method was called. + // Otherwise, it would prevent Object::call from calling native methods. + (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD; + return godot_bool.False; + } + + *ret = retValue; + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *ret = default; + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool Set(IntPtr godotObjectGCHandle, godot_string_name* name, godot_variant* value) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + throw new InvalidOperationException(); + + if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value))) + { + return godot_bool.True; + } + + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); + + Variant valueManaged = Variant.CreateCopyingBorrowed(*value); + + return godotObject._Set(nameManaged, valueManaged).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool Get(IntPtr godotObjectGCHandle, godot_string_name* name, + godot_variant* outRet) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + throw new InvalidOperationException(); + + if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue)) + { + *outRet = outRetValue; + return godot_bool.True; + } + + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); + + Variant ret = godotObject._Get(nameManaged); + + if (ret.VariantType == Variant.Type.Nil) + { + *outRet = default; + return godot_bool.False; + } + + *outRet = Marshaling.ConvertManagedObjectToVariant(ret); + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRet = default; + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static void CallDispose(IntPtr godotObjectGCHandle, godot_bool okIfNull) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (okIfNull.ToBool()) + godotObject?.Dispose(); + else + godotObject!.Dispose(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void CallToString(IntPtr godotObjectGCHandle, godot_string* outRes, godot_bool* outValid) + { + try + { + var self = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (self == null) + { + *outRes = default; + *outValid = godot_bool.False; + return; + } + + var resultStr = self.ToString(); + + if (resultStr == null) + { + *outRes = default; + *outValid = godot_bool.False; + return; + } + + *outRes = Marshaling.ConvertStringToNative(resultStr); + *outValid = godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRes = default; + *outValid = godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool HasMethodUnknownParams(IntPtr godotObjectGCHandle, godot_string_name* method) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + return godot_bool.False; + + return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void SerializeState( + IntPtr godotObjectGCHandle, + godot_dictionary* propertiesState, + godot_dictionary* signalEventsState + ) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + return; + + // Call OnBeforeSerialize + + // ReSharper disable once SuspiciousTypeConversion.Global + if (godotObject is ISerializationListener serializationListener) + serializationListener.OnBeforeSerialize(); + + // Save instance state + + using var info = GodotSerializationInfo.CreateCopyingBorrowed( + *propertiesState, *signalEventsState); + + godotObject.SaveGodotObjectData(info); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void DeserializeState( + IntPtr godotObjectGCHandle, + godot_dictionary* propertiesState, + godot_dictionary* signalEventsState + ) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + return; + + // Restore instance state + + using var info = GodotSerializationInfo.CreateCopyingBorrowed( + *propertiesState, *signalEventsState); + + godotObject.RestoreGodotObjectData(info); + + // Call OnAfterDeserialize + + // ReSharper disable once SuspiciousTypeConversion.Global + if (godotObject is ISerializationListener serializationListener) + serializationListener.OnAfterDeserialize(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs new file mode 100644 index 0000000000..456a118b90 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + internal static class GCHandleBridge + { + [UnmanagedCallersOnly] + internal static void FreeGCHandle(IntPtr gcHandlePtr) + { + try + { + CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr)); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs new file mode 100644 index 0000000000..6d20f95007 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs @@ -0,0 +1,87 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Godot.NativeInterop; + +namespace Godot.Bridge; + +public sealed class GodotSerializationInfo : IDisposable +{ + private readonly Collections.Dictionary _properties; + private readonly Collections.Dictionary _signalEvents; + + public void Dispose() + { + _properties?.Dispose(); + _signalEvents?.Dispose(); + + GC.SuppressFinalize(this); + } + + private GodotSerializationInfo(in godot_dictionary properties, in godot_dictionary signalEvents) + { + _properties = Collections.Dictionary.CreateTakingOwnershipOfDisposableValue(properties); + _signalEvents = Collections.Dictionary.CreateTakingOwnershipOfDisposableValue(signalEvents); + } + + internal static GodotSerializationInfo CreateCopyingBorrowed( + in godot_dictionary properties, in godot_dictionary signalEvents) + { + return new(NativeFuncs.godotsharp_dictionary_new_copy(properties), + NativeFuncs.godotsharp_dictionary_new_copy(signalEvents)); + } + + public void AddProperty(StringName name, Variant value) + { + _properties[name] = value; + } + + public bool TryGetProperty(StringName name, out Variant value) + { + return _properties.TryGetValue(name, out value); + } + + public void AddSignalEventDelegate(StringName name, Delegate eventDelegate) + { + var serializedData = new Collections.Array(); + + if (DelegateUtils.TrySerializeDelegate(eventDelegate, serializedData)) + { + _signalEvents[name] = serializedData; + } + else if (OS.IsStdoutVerbose()) + { + Console.WriteLine($"Failed to serialize event signal delegate: {name}"); + } + } + + public bool TryGetSignalEventDelegate<T>(StringName name, [MaybeNullWhen(false)] out T value) + where T : Delegate + { + if (_signalEvents.TryGetValue(name, out Variant serializedData)) + { + if (DelegateUtils.TryDeserializeDelegate(serializedData.AsGodotArray(), out var eventDelegate)) + { + value = eventDelegate as T; + + if (value == null) + { + Console.WriteLine($"Cannot cast the deserialized event signal delegate: {name}. " + + $"Expected '{typeof(T).FullName}'; got '{eventDelegate.GetType().FullName}'."); + return false; + } + + return true; + } + else if (OS.IsStdoutVerbose()) + { + Console.WriteLine($"Failed to deserialize event signal delegate: {name}"); + } + + value = null; + return false; + } + + value = null; + return false; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs new file mode 100644 index 0000000000..57240624bc --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct ManagedCallbacks + { + // @formatter:off + public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback; + public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs; + public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals; + public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle; + public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle; + public delegate* unmanaged<void> ScriptManagerBridge_FrameCallback; + public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding; + public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance; + public delegate* unmanaged<IntPtr, godot_string_name*, void> ScriptManagerBridge_GetScriptNativeName; + public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagerBridge_SetGodotObjectPtr; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal; + public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits; + public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge; + public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath; + public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge; + public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass; + public delegate* unmanaged<IntPtr, godot_bool*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo; + public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType; + public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList; + public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> CSharpInstanceBridge_Call; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Set; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get; + public delegate* unmanaged<IntPtr, godot_bool, void> CSharpInstanceBridge_CallDispose; + public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_bool> CSharpInstanceBridge_HasMethodUnknownParams; + public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_SerializeState; + public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_DeserializeState; + public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle; + public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo; + public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown; + public delegate* unmanaged<godot_bool, void> GD_OnCoreApiAssemblyLoaded; + // @formatter:on + + public static ManagedCallbacks Create() + { + return new() + { + // @formatter:off + SignalAwaiter_SignalCallback = &SignalAwaiter.SignalCallback, + DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs, + DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals, + DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle, + DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle, + ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback, + ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding, + ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance, + ScriptManagerBridge_GetScriptNativeName = &ScriptManagerBridge.GetScriptNativeName, + ScriptManagerBridge_SetGodotObjectPtr = &ScriptManagerBridge.SetGodotObjectPtr, + ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal, + ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits, + ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge, + ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath, + ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge, + ScriptManagerBridge_TryReloadRegisteredScriptWithClass = &ScriptManagerBridge.TryReloadRegisteredScriptWithClass, + ScriptManagerBridge_UpdateScriptClassInfo = &ScriptManagerBridge.UpdateScriptClassInfo, + ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType, + ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList, + ScriptManagerBridge_GetPropertyDefaultValues = &ScriptManagerBridge.GetPropertyDefaultValues, + CSharpInstanceBridge_Call = &CSharpInstanceBridge.Call, + CSharpInstanceBridge_Set = &CSharpInstanceBridge.Set, + CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get, + CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose, + CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString, + CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams, + CSharpInstanceBridge_SerializeState = &CSharpInstanceBridge.SerializeState, + CSharpInstanceBridge_DeserializeState = &CSharpInstanceBridge.DeserializeState, + GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle, + DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo, + DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown, + GD_OnCoreApiAssemblyLoaded = &GD.OnCoreApiAssemblyLoaded, + // @formatter:on + }; + } + + public static void Create(IntPtr outManagedCallbacks) + => *(ManagedCallbacks*)outManagedCallbacks = Create(); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs new file mode 100644 index 0000000000..647ae436ff --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Godot.Bridge; + +#nullable enable + +public struct MethodInfo +{ + public StringName Name { get; init; } + public PropertyInfo ReturnVal { get; init; } + public MethodFlags Flags { get; init; } + public int Id { get; init; } = 0; + public List<PropertyInfo>? Arguments { get; init; } + public List<Variant>? DefaultArguments { get; init; } + + public MethodInfo(StringName name, PropertyInfo returnVal, MethodFlags flags, + List<PropertyInfo>? arguments, List<Variant>? defaultArguments) + { + Name = name; + ReturnVal = returnVal; + Flags = flags; + Arguments = arguments; + DefaultArguments = defaultArguments; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs new file mode 100644 index 0000000000..80d6f7b4a5 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs @@ -0,0 +1,24 @@ +namespace Godot.Bridge; + +#nullable enable + +public struct PropertyInfo +{ + public Variant.Type Type { get; init; } + public StringName Name { get; init; } + public PropertyHint Hint { get; init; } + public string HintString { get; init; } + public PropertyUsageFlags Usage { get; init; } + public bool Exported { get; init; } + + public PropertyInfo(Variant.Type type, StringName name, PropertyHint hint, string hintString, + PropertyUsageFlags usage, bool exported) + { + Type = type; + Name = name; + Hint = hint; + HintString = hintString; + Usage = usage; + Exported = exported; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs new file mode 100644 index 0000000000..0dc5ba7678 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -0,0 +1,1028 @@ +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Runtime.Serialization; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + // TODO: Make class internal once we replace LookupScriptsInAssembly (the only public member) with source generators + public static partial class ScriptManagerBridge + { + private static ConcurrentDictionary<AssemblyLoadContext, ConcurrentDictionary<Type, byte>> + _alcData = new(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void OnAlcUnloading(AssemblyLoadContext alc) + { + if (_alcData.TryRemove(alc, out var typesInAlc)) + { + foreach (var type in typesInAlc.Keys) + { + if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) && + !_pathTypeBiMap.TryGetScriptPath(type, out _)) + { + // For scripts without a path, we need to keep the class qualified name for reloading + _scriptDataForReload.TryAdd(scriptPtr, + (type.Assembly.GetName().Name, type.FullName ?? type.ToString())); + } + + _pathTypeBiMap.RemoveByScriptType(type); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AddTypeForAlcReloading(Type type) + { + var alc = AssemblyLoadContext.GetLoadContext(type.Assembly); + if (alc == null) + return; + + var typesInAlc = _alcData.GetOrAdd(alc, + static alc => + { + alc.Unloading += OnAlcUnloading; + return new(); + }); + typesInAlc.TryAdd(type, 0); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void TrackAlcForUnloading(AssemblyLoadContext alc) + { + _ = _alcData.GetOrAdd(alc, + static alc => + { + alc.Unloading += OnAlcUnloading; + return new(); + }); + } + + private static ScriptTypeBiMap _scriptTypeBiMap = new(); + private static PathScriptTypeBiMap _pathTypeBiMap = new(); + + private static ConcurrentDictionary<IntPtr, (string? assemblyName, string classFullName)> + _scriptDataForReload = new(); + + [UnmanagedCallersOnly] + internal static void FrameCallback() + { + try + { + Dispatcher.DefaultGodotTaskScheduler?.Activate(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName, + IntPtr godotObject) + { + // TODO: Optimize with source generators and delegate pointers + + try + { + using var stringName = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName))); + string nativeTypeNameStr = stringName.ToString(); + + Type nativeType = TypeGetProxyClass(nativeTypeNameStr) ?? throw new InvalidOperationException( + "Wrapper class not found for type: " + nativeTypeNameStr); + var obj = (Object)FormatterServices.GetUninitializedObject(nativeType); + + var ctor = nativeType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + null, Type.EmptyTypes, null); + + obj.NativePtr = godotObject; + + _ = ctor!.Invoke(obj, null); + + return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj)); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return IntPtr.Zero; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool CreateManagedForGodotObjectScriptInstance(IntPtr scriptPtr, + IntPtr godotObject, + godot_variant** args, int argCount) + { + // TODO: Optimize with source generators and delegate pointers + + try + { + // Performance is not critical here as this will be replaced with source generators. + Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + var obj = (Object)FormatterServices.GetUninitializedObject(scriptType); + + var ctor = scriptType + .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(c => c.GetParameters().Length == argCount) + .FirstOrDefault(); + + if (ctor == null) + { + if (argCount == 0) + { + throw new MissingMemberException( + $"Cannot create script instance. The class '{scriptType.FullName}' does not define a parameterless constructor."); + } + else + { + throw new MissingMemberException( + $"The class '{scriptType.FullName}' does not define a constructor that takes x parameters."); + } + } + + var parameters = ctor.GetParameters(); + int paramCount = parameters.Length; + + var invokeParams = new object?[paramCount]; + + for (int i = 0; i < paramCount; i++) + { + invokeParams[i] = Marshaling.ConvertVariantToManagedObjectOfType( + *args[i], parameters[i].ParameterType); + } + + obj.NativePtr = godotObject; + + _ = ctor.Invoke(obj, invokeParams); + + + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetScriptNativeName(IntPtr scriptPtr, godot_string_name* outRes) + { + try + { + // Performance is not critical here as this will be replaced with source generators. + if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType)) + { + *outRes = default; + return; + } + + var native = Object.InternalGetClassNativeBase(scriptType); + + var field = native?.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.Public | BindingFlags.NonPublic); + + if (field == null) + { + *outRes = default; + return; + } + + var nativeName = (StringName?)field.GetValue(null); + + if (nativeName == null) + { + *outRes = default; + return; + } + + *outRes = NativeFuncs.godotsharp_string_name_new_copy((godot_string_name)nativeName.NativeValue); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRes = default; + } + } + + [UnmanagedCallersOnly] + internal static void SetGodotObjectPtr(IntPtr gcHandlePtr, IntPtr newPtr) + { + try + { + var target = (Object?)GCHandle.FromIntPtr(gcHandlePtr).Target; + if (target != null) + target.NativePtr = newPtr; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + private static Type? TypeGetProxyClass(string nativeTypeNameStr) + { + // Performance is not critical here as this will be replaced with a generated dictionary. + + if (nativeTypeNameStr[0] == '_') + nativeTypeNameStr = nativeTypeNameStr.Substring(1); + + Type? wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr); + + if (wrapperType == null) + { + wrapperType = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor")? + .GetType("Godot." + nativeTypeNameStr); + } + + static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed; + + if (wrapperType != null && IsStatic(wrapperType)) + { + // A static class means this is a Godot singleton class. If an instance is needed we use Godot.Object. + return typeof(Object); + } + + return wrapperType; + } + + // Called from GodotPlugins + // ReSharper disable once UnusedMember.Local + public static void LookupScriptsInAssembly(Assembly assembly) + { + static void LookupScriptForClass(Type type) + { + var scriptPathAttr = type.GetCustomAttributes(inherit: false) + .OfType<ScriptPathAttribute>() + .FirstOrDefault(); + + if (scriptPathAttr == null) + return; + + _pathTypeBiMap.Add(scriptPathAttr.Path, type); + + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + AddTypeForAlcReloading(type); + } + } + + var assemblyHasScriptsAttr = assembly.GetCustomAttributes(inherit: false) + .OfType<AssemblyHasScriptsAttribute>() + .FirstOrDefault(); + + if (assemblyHasScriptsAttr == null) + return; + + if (assemblyHasScriptsAttr.RequiresLookup) + { + // This is supported for scenarios where specifying all types would be cumbersome, + // such as when disabling C# source generators (for whatever reason) or when using a + // language other than C# that has nothing similar to source generators to automate it. + + var typeOfGodotObject = typeof(Object); + + foreach (var type in assembly.GetTypes()) + { + if (type.IsNested) + continue; + + if (!typeOfGodotObject.IsAssignableFrom(type)) + continue; + + LookupScriptForClass(type); + } + } + else + { + // This is the most likely scenario as we use C# source generators + + var scriptTypes = assemblyHasScriptsAttr.ScriptTypes; + + if (scriptTypes != null) + { + for (int i = 0; i < scriptTypes.Length; i++) + { + LookupScriptForClass(scriptTypes[i]); + } + } + } + } + + [UnmanagedCallersOnly] + internal static unsafe void RaiseEventSignal(IntPtr ownerGCHandlePtr, + godot_string_name* eventSignalName, godot_variant** args, int argCount, godot_bool* outOwnerIsNull) + { + try + { + var owner = (Object?)GCHandle.FromIntPtr(ownerGCHandlePtr).Target; + + if (owner == null) + { + *outOwnerIsNull = godot_bool.True; + return; + } + + *outOwnerIsNull = godot_bool.False; + + owner.RaiseGodotClassSignalCallbacks(CustomUnsafe.AsRef(eventSignalName), + new NativeVariantPtrArgs(args), argCount); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outOwnerIsNull = godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static godot_bool ScriptIsOrInherits(IntPtr scriptPtr, IntPtr scriptPtrMaybeBase) + { + try + { + if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType)) + return godot_bool.False; + + if (!_scriptTypeBiMap.TryGetScriptType(scriptPtrMaybeBase, out Type? maybeBaseType)) + return godot_bool.False; + + return (scriptType == maybeBaseType || maybeBaseType.IsAssignableFrom(scriptType)).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool AddScriptBridge(IntPtr scriptPtr, godot_string* scriptPath) + { + try + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr)) + { + string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath); + + if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType)) + return godot_bool.False; + + _scriptTypeBiMap.Add(scriptPtr, scriptType); + } + } + + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetOrCreateScriptBridgeForPath(godot_string* scriptPath, godot_ref* outScript) + { + string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath); + + if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType)) + { + NativeFuncs.godotsharp_internal_new_csharp_script(outScript); + return; + } + + GetOrCreateScriptBridgeForType(scriptType, outScript); + } + + private static unsafe void GetOrCreateScriptBridgeForType(Type scriptType, godot_ref* outScript) + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr)) + { + // Use existing + NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr); + return; + } + + // This path is slower, but it's only executed for the first instantiation of the type + CreateScriptBridgeForType(scriptType, outScript); + } + } + + internal static unsafe void GetOrLoadOrCreateScriptForType(Type scriptType, godot_ref* outScript) + { + static bool GetPathOtherwiseGetOrCreateScript(Type scriptType, godot_ref* outScript, + [MaybeNullWhen(false)] out string scriptPath) + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr)) + { + // Use existing + NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr); + scriptPath = null; + return false; + } + + // This path is slower, but it's only executed for the first instantiation of the type + + if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath)) + return true; + + CreateScriptBridgeForType(scriptType, outScript); + scriptPath = null; + return false; + } + } + + if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath)) + { + // This path is slower, but it's only executed for the first instantiation of the type + + // This must be done outside the read-write lock, as the script resource loading can lock it + using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath); + if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool()) + { + GD.PushError($"Cannot load script for type '{scriptType.FullName}'. Path: '{scriptPath}'."); + + // If loading of the script fails, best we can do create a new script + // with no path, as we do for types without an associated script file. + GetOrCreateScriptBridgeForType(scriptType, outScript); + } + } + } + + private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript) + { + NativeFuncs.godotsharp_internal_new_csharp_script(outScript); + IntPtr scriptPtr = outScript->Reference; + + // Caller takes care of locking + _scriptTypeBiMap.Add(scriptPtr, scriptType); + + NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr); + } + + [UnmanagedCallersOnly] + internal static void RemoveScriptBridge(IntPtr scriptPtr) + { + try + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + _scriptTypeBiMap.Remove(scriptPtr); + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static godot_bool TryReloadRegisteredScriptWithClass(IntPtr scriptPtr) + { + try + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (_scriptTypeBiMap.TryGetScriptType(scriptPtr, out _)) + { + // NOTE: + // Currently, we reload all scripts, not only the ones from the unloaded ALC. + // As such, we need to handle this case instead of treating it as an error. + NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr); + return godot_bool.True; + } + + if (!_scriptDataForReload.TryGetValue(scriptPtr, out var dataForReload)) + { + GD.PushError("Missing class qualified name for reloading script"); + return godot_bool.False; + } + + _ = _scriptDataForReload.TryRemove(scriptPtr, out _); + + if (dataForReload.assemblyName == null) + { + GD.PushError( + $"Missing assembly name of class '{dataForReload.classFullName}' for reloading script"); + return godot_bool.False; + } + + var scriptType = ReflectionUtils.FindTypeInLoadedAssemblies(dataForReload.assemblyName, + dataForReload.classFullName); + + if (scriptType == null) + { + // The class was removed, can't reload + return godot_bool.False; + } + + // ReSharper disable once RedundantNameQualifier + if (!typeof(Godot.Object).IsAssignableFrom(scriptType)) + { + // The class no longer inherits Godot.Object, can't reload + return godot_bool.False; + } + + _scriptTypeBiMap.Add(scriptPtr, scriptType); + + NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr); + + return godot_bool.True; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool, + godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest, + godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript) + { + try + { + // Performance is not critical here as this will be replaced with source generators. + var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + + *outTool = scriptType.GetCustomAttributes(inherit: false) + .OfType<ToolAttribute>() + .Any().ToGodotBool(); + + if (!(*outTool).ToBool() && scriptType.IsNested) + { + *outTool = (scriptType.DeclaringType?.GetCustomAttributes(inherit: false) + .OfType<ToolAttribute>() + .Any() ?? false).ToGodotBool(); + } + + if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools") + *outTool = godot_bool.True; + + // Methods + + // Performance is not critical here as this will be replaced with source generators. + using var methods = new Collections.Array(); + + Type? top = scriptType; + Type native = Object.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var methodList = GetMethodListForType(top); + + if (methodList != null) + { + foreach (var method in methodList) + { + var methodInfo = new Collections.Dictionary(); + + methodInfo.Add("name", method.Name); + + var methodParams = new Collections.Array(); + + if (method.Arguments != null) + { + foreach (var param in method.Arguments) + { + methodParams.Add(new Collections.Dictionary() + { + { "name", param.Name }, + { "type", (int)param.Type }, + { "usage", (int)param.Usage } + }); + } + } + + methodInfo.Add("params", methodParams); + + methods.Add(methodInfo); + } + } + + top = top.BaseType; + } + + *outMethodsDest = NativeFuncs.godotsharp_array_new_copy( + (godot_array)methods.NativeValue); + + // RPC functions + + Collections.Dictionary rpcFunctions = new(); + + top = scriptType; + + while (top != null && top != native) + { + foreach (var method in top.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public)) + { + if (method.IsStatic) + continue; + + string methodName = method.Name; + + if (rpcFunctions.ContainsKey(methodName)) + continue; + + var rpcAttr = method.GetCustomAttributes(inherit: false) + .OfType<RPCAttribute>().FirstOrDefault(); + + if (rpcAttr == null) + continue; + + var rpcConfig = new Collections.Dictionary(); + + rpcConfig["rpc_mode"] = (long)rpcAttr.Mode; + rpcConfig["call_local"] = rpcAttr.CallLocal; + rpcConfig["transfer_mode"] = (long)rpcAttr.TransferMode; + rpcConfig["channel"] = rpcAttr.TransferChannel; + + rpcFunctions.Add(methodName, rpcConfig); + } + + top = top.BaseType; + } + + *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new_copy( + (godot_dictionary)(rpcFunctions).NativeValue); + + // Event signals + + // Performance is not critical here as this will be replaced with source generators. + using var signals = new Collections.Dictionary(); + + top = scriptType; + + while (top != null && top != native) + { + var signalList = GetSignalListForType(top); + + if (signalList != null) + { + foreach (var signal in signalList) + { + string signalName = signal.Name; + + if (signals.ContainsKey(signalName)) + continue; + + var signalParams = new Collections.Array(); + + if (signal.Arguments != null) + { + foreach (var param in signal.Arguments) + { + signalParams.Add(new Collections.Dictionary() + { + { "name", param.Name }, + { "type", (int)param.Type }, + { "usage", (int)param.Usage } + }); + } + } + + signals.Add(signalName, signalParams); + } + } + + top = top.BaseType; + } + + *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new_copy( + (godot_dictionary)signals.NativeValue); + + // Base script + + var baseType = scriptType.BaseType; + if (baseType != null && baseType != native) + { + GetOrLoadOrCreateScriptForType(baseType, outBaseScript); + } + else + { + *outBaseScript = default; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outTool = godot_bool.False; + *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new(); + *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new(); + *outBaseScript = default; + } + } + + private static List<MethodInfo>? GetSignalListForType(Type type) + { + var getGodotSignalListMethod = type.GetMethod( + "GetGodotSignalList", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotSignalListMethod == null) + return null; + + return (List<MethodInfo>?)getGodotSignalListMethod.Invoke(null, null); + } + + private static List<MethodInfo>? GetMethodListForType(Type type) + { + var getGodotMethodListMethod = type.GetMethod( + "GetGodotMethodList", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotMethodListMethod == null) + return null; + + return (List<MethodInfo>?)getGodotMethodListMethod.Invoke(null, null); + } + + // ReSharper disable once InconsistentNaming + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + [StructLayout(LayoutKind.Sequential)] + private ref struct godotsharp_property_info + { + // Careful with padding... + public godot_string_name Name; // Not owned + public godot_string HintString; + public int Type; + public int Hint; + public int Usage; + public godot_bool Exported; + + public void Dispose() + { + HintString.Dispose(); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetPropertyInfoList(IntPtr scriptPtr, + delegate* unmanaged<IntPtr, godot_string*, void*, int, void> addPropInfoFunc) + { + try + { + Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + GetPropertyInfoListForType(scriptType, scriptPtr, addPropInfoFunc); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + private static unsafe void GetPropertyInfoListForType(Type type, IntPtr scriptPtr, + delegate* unmanaged<IntPtr, godot_string*, void*, int, void> addPropInfoFunc) + { + try + { + var getGodotPropertyListMethod = type.GetMethod( + "GetGodotPropertyList", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotPropertyListMethod == null) + return; + + var properties = (List<PropertyInfo>?) + getGodotPropertyListMethod.Invoke(null, null); + + if (properties == null || properties.Count <= 0) + return; + + int length = properties.Count; + + // There's no recursion here, so it's ok to go with a big enough number for most cases + // stackMaxSize = stackMaxLength * sizeof(godotsharp_property_info) + const int stackMaxLength = 32; + bool useStack = length < stackMaxLength; + + godotsharp_property_info* interopProperties; + + if (useStack) + { + // Weird limitation, hence the need for aux: + // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." + var aux = stackalloc godotsharp_property_info[length]; + interopProperties = aux; + } + else + { + interopProperties = ((godotsharp_property_info*)NativeMemory.Alloc((nuint)length))!; + } + + try + { + for (int i = 0; i < length; i++) + { + var property = properties[i]; + + godotsharp_property_info interopProperty = new() + { + Type = (int)property.Type, + Name = (godot_string_name)property.Name.NativeValue, // Not owned + Hint = (int)property.Hint, + HintString = Marshaling.ConvertStringToNative(property.HintString), + Usage = (int)property.Usage, + Exported = property.Exported.ToGodotBool() + }; + + interopProperties[i] = interopProperty; + } + + using godot_string currentClassName = Marshaling.ConvertStringToNative(type.Name); + + addPropInfoFunc(scriptPtr, ¤tClassName, interopProperties, length); + + // We're borrowing the StringName's without making an owning copy, so the + // managed collection needs to be kept alive until `addPropInfoFunc` returns. + GC.KeepAlive(properties); + } + finally + { + for (int i = 0; i < length; i++) + interopProperties[i].Dispose(); + + if (!useStack) + NativeMemory.Free(interopProperties); + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + // ReSharper disable once InconsistentNaming + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + [StructLayout(LayoutKind.Sequential)] + private ref struct godotsharp_property_def_val_pair + { + // Careful with padding... + public godot_string_name Name; // Not owned + public godot_variant Value; + + public void Dispose() + { + Value.Dispose(); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetPropertyDefaultValues(IntPtr scriptPtr, + delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc) + { + try + { + Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr); + Type native = Object.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + GetPropertyDefaultValuesForType(top, scriptPtr, addDefValFunc); + + top = top.BaseType; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [SkipLocalsInit] + private static unsafe void GetPropertyDefaultValuesForType(Type type, IntPtr scriptPtr, + delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc) + { + try + { + var getGodotPropertyDefaultValuesMethod = type.GetMethod( + "GetGodotPropertyDefaultValues", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotPropertyDefaultValuesMethod == null) + return; + + var defaultValues = (Dictionary<StringName, object>?) + getGodotPropertyDefaultValuesMethod.Invoke(null, null); + + if (defaultValues == null || defaultValues.Count <= 0) + return; + + int length = defaultValues.Count; + + // There's no recursion here, so it's ok to go with a big enough number for most cases + // stackMaxSize = stackMaxLength * sizeof(godotsharp_property_def_val_pair) + const int stackMaxLength = 32; + bool useStack = length < stackMaxLength; + + godotsharp_property_def_val_pair* interopDefaultValues; + + if (useStack) + { + // Weird limitation, hence the need for aux: + // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." + var aux = stackalloc godotsharp_property_def_val_pair[length]; + interopDefaultValues = aux; + } + else + { + interopDefaultValues = ((godotsharp_property_def_val_pair*)NativeMemory.Alloc((nuint)length))!; + } + + try + { + int i = 0; + foreach (var defaultValuePair in defaultValues) + { + godotsharp_property_def_val_pair interopProperty = new() + { + Name = (godot_string_name)defaultValuePair.Key.NativeValue, // Not owned + Value = Marshaling.ConvertManagedObjectToVariant(defaultValuePair.Value) + }; + + interopDefaultValues[i] = interopProperty; + + i++; + } + + addDefValFunc(scriptPtr, interopDefaultValues, length); + + // We're borrowing the StringName's without making an owning copy, so the + // managed collection needs to be kept alive until `addDefValFunc` returns. + GC.KeepAlive(defaultValues); + } + finally + { + for (int i = 0; i < length; i++) + interopDefaultValues[i].Dispose(); + + if (!useStack) + NativeMemory.Free(interopDefaultValues); + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool SwapGCHandleForType(IntPtr oldGCHandlePtr, IntPtr* outNewGCHandlePtr, + godot_bool createWeak) + { + try + { + var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr); + + object? target = oldGCHandle.Target; + + if (target == null) + { + CustomGCHandle.Free(oldGCHandle); + *outNewGCHandlePtr = IntPtr.Zero; + return godot_bool.False; // Called after the managed side was collected, so nothing to do here + } + + // Release the current weak handle and replace it with a strong handle. + var newGCHandle = createWeak.ToBool() ? + CustomGCHandle.AllocWeak(target) : + CustomGCHandle.AllocStrong(target); + + CustomGCHandle.Free(oldGCHandle); + *outNewGCHandlePtr = GCHandle.ToIntPtr(newGCHandle); + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outNewGCHandlePtr = IntPtr.Zero; + return godot_bool.False; + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs new file mode 100644 index 0000000000..a58f6849ad --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Godot.Bridge; + +#nullable enable + +public static partial class ScriptManagerBridge +{ + private class ScriptTypeBiMap + { + public readonly object ReadWriteLock = new(); + private System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new(); + private System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new(); + + public void Add(IntPtr scriptPtr, Type scriptType) + { + // TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading? + + _scriptTypeMap.Add(scriptPtr, scriptType); + _typeScriptMap.Add(scriptType, scriptPtr); + + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + AddTypeForAlcReloading(scriptType); + } + } + + public void Remove(IntPtr scriptPtr) + { + if (_scriptTypeMap.Remove(scriptPtr, out Type? scriptType)) + _ = _typeScriptMap.Remove(scriptType); + } + + public bool RemoveByScriptType(Type scriptType, out IntPtr scriptPtr) + { + if (_typeScriptMap.Remove(scriptType, out scriptPtr)) + return _scriptTypeMap.Remove(scriptPtr); + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Type GetScriptType(IntPtr scriptPtr) => _scriptTypeMap[scriptPtr]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptType(IntPtr scriptPtr, [MaybeNullWhen(false)] out Type scriptType) => + _scriptTypeMap.TryGetValue(scriptPtr, out scriptType); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptPtr(Type scriptType, out IntPtr scriptPtr) => + _typeScriptMap.TryGetValue(scriptType, out scriptPtr); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsScriptRegistered(IntPtr scriptPtr) => _scriptTypeMap.ContainsKey(scriptPtr); + } + + private class PathScriptTypeBiMap + { + private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new(); + private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new(); + + public void Add(string scriptPath, Type scriptType) + { + _pathTypeMap.Add(scriptPath, scriptType); + + // Due to partial classes, more than one file can point to the same type, so + // there could be duplicate keys in this case. We only add a type as key once. + _typePathMap.TryAdd(scriptType, scriptPath); + } + + public void RemoveByScriptType(Type scriptType) + { + foreach (var pair in _pathTypeMap + .Where(p => p.Value == scriptType).ToArray()) + { + _pathTypeMap.Remove(pair.Key); + } + + _typePathMap.Remove(scriptType); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) => + _pathTypeMap.TryGetValue(scriptPath, out scriptType); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath) => + _typePathMap.TryGetValue(scriptType, out scriptPath); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index 2722b64e6d..1b7f5158fd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -1,5 +1,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { @@ -24,7 +26,7 @@ namespace Godot /// } /// </code> /// </example> - public struct Callable + public readonly struct Callable { private readonly Object _target; private readonly StringName _method; @@ -34,10 +36,12 @@ namespace Godot /// Object that contains the method. /// </summary> public Object Target => _target; + /// <summary> /// Name of the method that will be called. /// </summary> public StringName Method => _method; + /// <summary> /// Delegate of the method that will be called. /// </summary> @@ -73,15 +77,43 @@ namespace Godot _delegate = @delegate; } + private const int VarArgsSpanThreshold = 5; + /// <summary> /// Calls the method represented by this <see cref="Callable"/>. /// Arguments can be passed and should match the method's signature. /// </summary> /// <param name="args">Arguments that will be passed to the method call.</param> /// <returns>The value returned by the method.</returns> - public object Call(params object[] args) + public unsafe Variant Call(params Variant[] args) { - return godot_icall_Callable_Call(ref this, args); + using godot_callable callable = Marshaling.ConvertCallableToNative(this); + + int argc = args.Length; + + Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? + stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + new godot_variant.movable[argc]; + + Span<IntPtr> argsSpan = argc <= 10 ? + stackalloc IntPtr[argc] : + new IntPtr[argc]; + + using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); + + fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) + fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) + { + for (int i = 0; i < argc; i++) + { + varargs[i] = (godot_variant)args[i].NativeVar; + argsPtr[i] = new IntPtr(&varargs[i]); + } + + godot_variant ret = NativeFuncs.godotsharp_callable_call(callable, + (godot_variant**)argsPtr, argc, out _); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); + } } /// <summary> @@ -89,15 +121,33 @@ namespace Godot /// Arguments can be passed and should match the method's signature. /// </summary> /// <param name="args">Arguments that will be passed to the method call.</param> - public void CallDeferred(params object[] args) + public unsafe void CallDeferred(params Variant[] args) { - godot_icall_Callable_CallDeferred(ref this, args); - } + using godot_callable callable = Marshaling.ConvertCallableToNative(this); + + int argc = args.Length; + + Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? + stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + new godot_variant.movable[argc]; - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Callable_Call(ref Callable callable, object[] args); + Span<IntPtr> argsSpan = argc <= 10 ? + stackalloc IntPtr[argc] : + new IntPtr[argc]; - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Callable_CallDeferred(ref Callable callable, object[] args); + using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); + + fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) + fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) + { + for (int i = 0; i < argc; i++) + { + varargs[i] = (godot_variant)args[i].NativeVar; + argsPtr[i] = new IntPtr(&varargs[i]); + } + + NativeFuncs.godotsharp_callable_call_deferred(callable, (godot_variant**)argsPtr, argc); + } + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index a6324504fc..ed0e1efd35 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -210,7 +210,7 @@ namespace Godot case 3: return a; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -230,7 +230,7 @@ namespace Godot a = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } @@ -841,7 +841,7 @@ namespace Godot return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1); } - private string ToHex32(float val) + private static string ToHex32(float val) { byte b = (byte)Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255)); return b.HexEncode(); @@ -849,7 +849,7 @@ namespace Godot internal static bool HtmlIsValid(string color) { - if (color.Length == 0) + if (string.IsNullOrEmpty(color)) { return false; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs index 68c821b447..bacf7c89e6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs @@ -10,152 +10,152 @@ namespace Godot { // Color names and values are derived from core/math/color_names.inc internal static readonly Dictionary<string, Color> namedColors = new Dictionary<string, Color> { - {"ALICEBLUE", new Color(0.94f, 0.97f, 1.00f)}, - {"ANTIQUEWHITE", new Color(0.98f, 0.92f, 0.84f)}, - {"AQUA", new Color(0.00f, 1.00f, 1.00f)}, - {"AQUAMARINE", new Color(0.50f, 1.00f, 0.83f)}, - {"AZURE", new Color(0.94f, 1.00f, 1.00f)}, - {"BEIGE", new Color(0.96f, 0.96f, 0.86f)}, - {"BISQUE", new Color(1.00f, 0.89f, 0.77f)}, - {"BLACK", new Color(0.00f, 0.00f, 0.00f)}, - {"BLANCHEDALMOND", new Color(1.00f, 0.92f, 0.80f)}, - {"BLUE", new Color(0.00f, 0.00f, 1.00f)}, - {"BLUEVIOLET", new Color(0.54f, 0.17f, 0.89f)}, - {"BROWN", new Color(0.65f, 0.16f, 0.16f)}, - {"BURLYWOOD", new Color(0.87f, 0.72f, 0.53f)}, - {"CADETBLUE", new Color(0.37f, 0.62f, 0.63f)}, - {"CHARTREUSE", new Color(0.50f, 1.00f, 0.00f)}, - {"CHOCOLATE", new Color(0.82f, 0.41f, 0.12f)}, - {"CORAL", new Color(1.00f, 0.50f, 0.31f)}, - {"CORNFLOWERBLUE", new Color(0.39f, 0.58f, 0.93f)}, - {"CORNSILK", new Color(1.00f, 0.97f, 0.86f)}, - {"CRIMSON", new Color(0.86f, 0.08f, 0.24f)}, - {"CYAN", new Color(0.00f, 1.00f, 1.00f)}, - {"DARKBLUE", new Color(0.00f, 0.00f, 0.55f)}, - {"DARKCYAN", new Color(0.00f, 0.55f, 0.55f)}, - {"DARKGOLDENROD", new Color(0.72f, 0.53f, 0.04f)}, - {"DARKGRAY", new Color(0.66f, 0.66f, 0.66f)}, - {"DARKGREEN", new Color(0.00f, 0.39f, 0.00f)}, - {"DARKKHAKI", new Color(0.74f, 0.72f, 0.42f)}, - {"DARKMAGENTA", new Color(0.55f, 0.00f, 0.55f)}, - {"DARKOLIVEGREEN", new Color(0.33f, 0.42f, 0.18f)}, - {"DARKORANGE", new Color(1.00f, 0.55f, 0.00f)}, - {"DARKORCHID", new Color(0.60f, 0.20f, 0.80f)}, - {"DARKRED", new Color(0.55f, 0.00f, 0.00f)}, - {"DARKSALMON", new Color(0.91f, 0.59f, 0.48f)}, - {"DARKSEAGREEN", new Color(0.56f, 0.74f, 0.56f)}, - {"DARKSLATEBLUE", new Color(0.28f, 0.24f, 0.55f)}, - {"DARKSLATEGRAY", new Color(0.18f, 0.31f, 0.31f)}, - {"DARKTURQUOISE", new Color(0.00f, 0.81f, 0.82f)}, - {"DARKVIOLET", new Color(0.58f, 0.00f, 0.83f)}, - {"DEEPPINK", new Color(1.00f, 0.08f, 0.58f)}, - {"DEEPSKYBLUE", new Color(0.00f, 0.75f, 1.00f)}, - {"DIMGRAY", new Color(0.41f, 0.41f, 0.41f)}, - {"DODGERBLUE", new Color(0.12f, 0.56f, 1.00f)}, - {"FIREBRICK", new Color(0.70f, 0.13f, 0.13f)}, - {"FLORALWHITE", new Color(1.00f, 0.98f, 0.94f)}, - {"FORESTGREEN", new Color(0.13f, 0.55f, 0.13f)}, - {"FUCHSIA", new Color(1.00f, 0.00f, 1.00f)}, - {"GAINSBORO", new Color(0.86f, 0.86f, 0.86f)}, - {"GHOSTWHITE", new Color(0.97f, 0.97f, 1.00f)}, - {"GOLD", new Color(1.00f, 0.84f, 0.00f)}, - {"GOLDENROD", new Color(0.85f, 0.65f, 0.13f)}, - {"GRAY", new Color(0.75f, 0.75f, 0.75f)}, - {"GREEN", new Color(0.00f, 1.00f, 0.00f)}, - {"GREENYELLOW", new Color(0.68f, 1.00f, 0.18f)}, - {"HONEYDEW", new Color(0.94f, 1.00f, 0.94f)}, - {"HOTPINK", new Color(1.00f, 0.41f, 0.71f)}, - {"INDIANRED", new Color(0.80f, 0.36f, 0.36f)}, - {"INDIGO", new Color(0.29f, 0.00f, 0.51f)}, - {"IVORY", new Color(1.00f, 1.00f, 0.94f)}, - {"KHAKI", new Color(0.94f, 0.90f, 0.55f)}, - {"LAVENDER", new Color(0.90f, 0.90f, 0.98f)}, - {"LAVENDERBLUSH", new Color(1.00f, 0.94f, 0.96f)}, - {"LAWNGREEN", new Color(0.49f, 0.99f, 0.00f)}, - {"LEMONCHIFFON", new Color(1.00f, 0.98f, 0.80f)}, - {"LIGHTBLUE", new Color(0.68f, 0.85f, 0.90f)}, - {"LIGHTCORAL", new Color(0.94f, 0.50f, 0.50f)}, - {"LIGHTCYAN", new Color(0.88f, 1.00f, 1.00f)}, - {"LIGHTGOLDENROD", new Color(0.98f, 0.98f, 0.82f)}, - {"LIGHTGRAY", new Color(0.83f, 0.83f, 0.83f)}, - {"LIGHTGREEN", new Color(0.56f, 0.93f, 0.56f)}, - {"LIGHTPINK", new Color(1.00f, 0.71f, 0.76f)}, - {"LIGHTSALMON", new Color(1.00f, 0.63f, 0.48f)}, - {"LIGHTSEAGREEN", new Color(0.13f, 0.70f, 0.67f)}, - {"LIGHTSKYBLUE", new Color(0.53f, 0.81f, 0.98f)}, - {"LIGHTSLATEGRAY", new Color(0.47f, 0.53f, 0.60f)}, - {"LIGHTSTEELBLUE", new Color(0.69f, 0.77f, 0.87f)}, - {"LIGHTYELLOW", new Color(1.00f, 1.00f, 0.88f)}, - {"LIME", new Color(0.00f, 1.00f, 0.00f)}, - {"LIMEGREEN", new Color(0.20f, 0.80f, 0.20f)}, - {"LINEN", new Color(0.98f, 0.94f, 0.90f)}, - {"MAGENTA", new Color(1.00f, 0.00f, 1.00f)}, - {"MAROON", new Color(0.69f, 0.19f, 0.38f)}, - {"MEDIUMAQUAMARINE", new Color(0.40f, 0.80f, 0.67f)}, - {"MEDIUMBLUE", new Color(0.00f, 0.00f, 0.80f)}, - {"MEDIUMORCHID", new Color(0.73f, 0.33f, 0.83f)}, - {"MEDIUMPURPLE", new Color(0.58f, 0.44f, 0.86f)}, - {"MEDIUMSEAGREEN", new Color(0.24f, 0.70f, 0.44f)}, - {"MEDIUMSLATEBLUE", new Color(0.48f, 0.41f, 0.93f)}, - {"MEDIUMSPRINGGREEN", new Color(0.00f, 0.98f, 0.60f)}, - {"MEDIUMTURQUOISE", new Color(0.28f, 0.82f, 0.80f)}, - {"MEDIUMVIOLETRED", new Color(0.78f, 0.08f, 0.52f)}, - {"MIDNIGHTBLUE", new Color(0.10f, 0.10f, 0.44f)}, - {"MINTCREAM", new Color(0.96f, 1.00f, 0.98f)}, - {"MISTYROSE", new Color(1.00f, 0.89f, 0.88f)}, - {"MOCCASIN", new Color(1.00f, 0.89f, 0.71f)}, - {"NAVAJOWHITE", new Color(1.00f, 0.87f, 0.68f)}, - {"NAVYBLUE", new Color(0.00f, 0.00f, 0.50f)}, - {"OLDLACE", new Color(0.99f, 0.96f, 0.90f)}, - {"OLIVE", new Color(0.50f, 0.50f, 0.00f)}, - {"OLIVEDRAB", new Color(0.42f, 0.56f, 0.14f)}, - {"ORANGE", new Color(1.00f, 0.65f, 0.00f)}, - {"ORANGERED", new Color(1.00f, 0.27f, 0.00f)}, - {"ORCHID", new Color(0.85f, 0.44f, 0.84f)}, - {"PALEGOLDENROD", new Color(0.93f, 0.91f, 0.67f)}, - {"PALEGREEN", new Color(0.60f, 0.98f, 0.60f)}, - {"PALETURQUOISE", new Color(0.69f, 0.93f, 0.93f)}, - {"PALEVIOLETRED", new Color(0.86f, 0.44f, 0.58f)}, - {"PAPAYAWHIP", new Color(1.00f, 0.94f, 0.84f)}, - {"PEACHPUFF", new Color(1.00f, 0.85f, 0.73f)}, - {"PERU", new Color(0.80f, 0.52f, 0.25f)}, - {"PINK", new Color(1.00f, 0.75f, 0.80f)}, - {"PLUM", new Color(0.87f, 0.63f, 0.87f)}, - {"POWDERBLUE", new Color(0.69f, 0.88f, 0.90f)}, - {"PURPLE", new Color(0.63f, 0.13f, 0.94f)}, - {"REBECCAPURPLE", new Color(0.40f, 0.20f, 0.60f)}, - {"RED", new Color(1.00f, 0.00f, 0.00f)}, - {"ROSYBROWN", new Color(0.74f, 0.56f, 0.56f)}, - {"ROYALBLUE", new Color(0.25f, 0.41f, 0.88f)}, - {"SADDLEBROWN", new Color(0.55f, 0.27f, 0.07f)}, - {"SALMON", new Color(0.98f, 0.50f, 0.45f)}, - {"SANDYBROWN", new Color(0.96f, 0.64f, 0.38f)}, - {"SEAGREEN", new Color(0.18f, 0.55f, 0.34f)}, - {"SEASHELL", new Color(1.00f, 0.96f, 0.93f)}, - {"SIENNA", new Color(0.63f, 0.32f, 0.18f)}, - {"SILVER", new Color(0.75f, 0.75f, 0.75f)}, - {"SKYBLUE", new Color(0.53f, 0.81f, 0.92f)}, - {"SLATEBLUE", new Color(0.42f, 0.35f, 0.80f)}, - {"SLATEGRAY", new Color(0.44f, 0.50f, 0.56f)}, - {"SNOW", new Color(1.00f, 0.98f, 0.98f)}, - {"SPRINGGREEN", new Color(0.00f, 1.00f, 0.50f)}, - {"STEELBLUE", new Color(0.27f, 0.51f, 0.71f)}, - {"TAN", new Color(0.82f, 0.71f, 0.55f)}, - {"TEAL", new Color(0.00f, 0.50f, 0.50f)}, - {"THISTLE", new Color(0.85f, 0.75f, 0.85f)}, - {"TOMATO", new Color(1.00f, 0.39f, 0.28f)}, - {"TRANSPARENT", new Color(1.00f, 1.00f, 1.00f, 0.00f)}, - {"TURQUOISE", new Color(0.25f, 0.88f, 0.82f)}, - {"VIOLET", new Color(0.93f, 0.51f, 0.93f)}, - {"WEBGRAY", new Color(0.50f, 0.50f, 0.50f)}, - {"WEBGREEN", new Color(0.00f, 0.50f, 0.00f)}, - {"WEBMAROON", new Color(0.50f, 0.00f, 0.00f)}, - {"WEBPURPLE", new Color(0.50f, 0.00f, 0.50f)}, - {"WHEAT", new Color(0.96f, 0.87f, 0.70f)}, - {"WHITE", new Color(1.00f, 1.00f, 1.00f)}, - {"WHITESMOKE", new Color(0.96f, 0.96f, 0.96f)}, - {"YELLOW", new Color(1.00f, 1.00f, 0.00f)}, - {"YELLOWGREEN", new Color(0.60f, 0.80f, 0.20f)}, + { "ALICE_BLUE", new Color(0xF0F8FFFF) }, + { "ANTIQUE_WHITE", new Color(0xFAEBD7FF) }, + { "AQUA", new Color(0x00FFFFFF) }, + { "AQUAMARINE", new Color(0x7FFFD4FF) }, + { "AZURE", new Color(0xF0FFFFFF) }, + { "BEIGE", new Color(0xF5F5DCFF) }, + { "BISQUE", new Color(0xFFE4C4FF) }, + { "BLACK", new Color(0x000000FF) }, + { "BLANCHED_ALMOND", new Color(0xFFEBCDFF) }, + { "BLUE", new Color(0x0000FFFF) }, + { "BLUE_VIOLET", new Color(0x8A2BE2FF) }, + { "BROWN", new Color(0xA52A2AFF) }, + { "BURLYWOOD", new Color(0xDEB887FF) }, + { "CADET_BLUE", new Color(0x5F9EA0FF) }, + { "CHARTREUSE", new Color(0x7FFF00FF) }, + { "CHOCOLATE", new Color(0xD2691EFF) }, + { "CORAL", new Color(0xFF7F50FF) }, + { "CORNFLOWER_BLUE", new Color(0x6495EDFF) }, + { "CORNSILK", new Color(0xFFF8DCFF) }, + { "CRIMSON", new Color(0xDC143CFF) }, + { "CYAN", new Color(0x00FFFFFF) }, + { "DARK_BLUE", new Color(0x00008BFF) }, + { "DARK_CYAN", new Color(0x008B8BFF) }, + { "DARK_GOLDENROD", new Color(0xB8860BFF) }, + { "DARK_GRAY", new Color(0xA9A9A9FF) }, + { "DARK_GREEN", new Color(0x006400FF) }, + { "DARK_KHAKI", new Color(0xBDB76BFF) }, + { "DARK_MAGENTA", new Color(0x8B008BFF) }, + { "DARK_OLIVE_GREEN", new Color(0x556B2FFF) }, + { "DARK_ORANGE", new Color(0xFF8C00FF) }, + { "DARK_ORCHID", new Color(0x9932CCFF) }, + { "DARK_RED", new Color(0x8B0000FF) }, + { "DARK_SALMON", new Color(0xE9967AFF) }, + { "DARK_SEA_GREEN", new Color(0x8FBC8FFF) }, + { "DARK_SLATE_BLUE", new Color(0x483D8BFF) }, + { "DARK_SLATE_GRAY", new Color(0x2F4F4FFF) }, + { "DARK_TURQUOISE", new Color(0x00CED1FF) }, + { "DARK_VIOLET", new Color(0x9400D3FF) }, + { "DEEP_PINK", new Color(0xFF1493FF) }, + { "DEEP_SKY_BLUE", new Color(0x00BFFFFF) }, + { "DIM_GRAY", new Color(0x696969FF) }, + { "DODGER_BLUE", new Color(0x1E90FFFF) }, + { "FIREBRICK", new Color(0xB22222FF) }, + { "FLORAL_WHITE", new Color(0xFFFAF0FF) }, + { "FOREST_GREEN", new Color(0x228B22FF) }, + { "FUCHSIA", new Color(0xFF00FFFF) }, + { "GAINSBORO", new Color(0xDCDCDCFF) }, + { "GHOST_WHITE", new Color(0xF8F8FFFF) }, + { "GOLD", new Color(0xFFD700FF) }, + { "GOLDENROD", new Color(0xDAA520FF) }, + { "GRAY", new Color(0xBEBEBEFF) }, + { "GREEN", new Color(0x00FF00FF) }, + { "GREEN_YELLOW", new Color(0xADFF2FFF) }, + { "HONEYDEW", new Color(0xF0FFF0FF) }, + { "HOT_PINK", new Color(0xFF69B4FF) }, + { "INDIAN_RED", new Color(0xCD5C5CFF) }, + { "INDIGO", new Color(0x4B0082FF) }, + { "IVORY", new Color(0xFFFFF0FF) }, + { "KHAKI", new Color(0xF0E68CFF) }, + { "LAVENDER", new Color(0xE6E6FAFF) }, + { "LAVENDER_BLUSH", new Color(0xFFF0F5FF) }, + { "LAWN_GREEN", new Color(0x7CFC00FF) }, + { "LEMON_CHIFFON", new Color(0xFFFACDFF) }, + { "LIGHT_BLUE", new Color(0xADD8E6FF) }, + { "LIGHT_CORAL", new Color(0xF08080FF) }, + { "LIGHT_CYAN", new Color(0xE0FFFFFF) }, + { "LIGHT_GOLDENROD", new Color(0xFAFAD2FF) }, + { "LIGHT_GRAY", new Color(0xD3D3D3FF) }, + { "LIGHT_GREEN", new Color(0x90EE90FF) }, + { "LIGHT_PINK", new Color(0xFFB6C1FF) }, + { "LIGHT_SALMON", new Color(0xFFA07AFF) }, + { "LIGHT_SEA_GREEN", new Color(0x20B2AAFF) }, + { "LIGHT_SKY_BLUE", new Color(0x87CEFAFF) }, + { "LIGHT_SLATE_GRAY", new Color(0x778899FF) }, + { "LIGHT_STEEL_BLUE", new Color(0xB0C4DEFF) }, + { "LIGHT_YELLOW", new Color(0xFFFFE0FF) }, + { "LIME", new Color(0x00FF00FF) }, + { "LIME_GREEN", new Color(0x32CD32FF) }, + { "LINEN", new Color(0xFAF0E6FF) }, + { "MAGENTA", new Color(0xFF00FFFF) }, + { "MAROON", new Color(0xB03060FF) }, + { "MEDIUM_AQUAMARINE", new Color(0x66CDAAFF) }, + { "MEDIUM_BLUE", new Color(0x0000CDFF) }, + { "MEDIUM_ORCHID", new Color(0xBA55D3FF) }, + { "MEDIUM_PURPLE", new Color(0x9370DBFF) }, + { "MEDIUM_SEA_GREEN", new Color(0x3CB371FF) }, + { "MEDIUM_SLATE_BLUE", new Color(0x7B68EEFF) }, + { "MEDIUM_SPRING_GREEN", new Color(0x00FA9AFF) }, + { "MEDIUM_TURQUOISE", new Color(0x48D1CCFF) }, + { "MEDIUM_VIOLET_RED", new Color(0xC71585FF) }, + { "MIDNIGHT_BLUE", new Color(0x191970FF) }, + { "MINT_CREAM", new Color(0xF5FFFAFF) }, + { "MISTY_ROSE", new Color(0xFFE4E1FF) }, + { "MOCCASIN", new Color(0xFFE4B5FF) }, + { "NAVAJO_WHITE", new Color(0xFFDEADFF) }, + { "NAVY_BLUE", new Color(0x000080FF) }, + { "OLD_LACE", new Color(0xFDF5E6FF) }, + { "OLIVE", new Color(0x808000FF) }, + { "OLIVE_DRAB", new Color(0x6B8E23FF) }, + { "ORANGE", new Color(0xFFA500FF) }, + { "ORANGE_RED", new Color(0xFF4500FF) }, + { "ORCHID", new Color(0xDA70D6FF) }, + { "PALE_GOLDENROD", new Color(0xEEE8AAFF) }, + { "PALE_GREEN", new Color(0x98FB98FF) }, + { "PALE_TURQUOISE", new Color(0xAFEEEEFF) }, + { "PALE_VIOLET_RED", new Color(0xDB7093FF) }, + { "PAPAYA_WHIP", new Color(0xFFEFD5FF) }, + { "PEACH_PUFF", new Color(0xFFDAB9FF) }, + { "PERU", new Color(0xCD853FFF) }, + { "PINK", new Color(0xFFC0CBFF) }, + { "PLUM", new Color(0xDDA0DDFF) }, + { "POWDER_BLUE", new Color(0xB0E0E6FF) }, + { "PURPLE", new Color(0xA020F0FF) }, + { "REBECCA_PURPLE", new Color(0x663399FF) }, + { "RED", new Color(0xFF0000FF) }, + { "ROSY_BROWN", new Color(0xBC8F8FFF) }, + { "ROYAL_BLUE", new Color(0x4169E1FF) }, + { "SADDLE_BROWN", new Color(0x8B4513FF) }, + { "SALMON", new Color(0xFA8072FF) }, + { "SANDY_BROWN", new Color(0xF4A460FF) }, + { "SEA_GREEN", new Color(0x2E8B57FF) }, + { "SEASHELL", new Color(0xFFF5EEFF) }, + { "SIENNA", new Color(0xA0522DFF) }, + { "SILVER", new Color(0xC0C0C0FF) }, + { "SKY_BLUE", new Color(0x87CEEBFF) }, + { "SLATE_BLUE", new Color(0x6A5ACDFF) }, + { "SLATE_GRAY", new Color(0x708090FF) }, + { "SNOW", new Color(0xFFFAFAFF) }, + { "SPRING_GREEN", new Color(0x00FF7FFF) }, + { "STEEL_BLUE", new Color(0x4682B4FF) }, + { "TAN", new Color(0xD2B48CFF) }, + { "TEAL", new Color(0x008080FF) }, + { "THISTLE", new Color(0xD8BFD8FF) }, + { "TOMATO", new Color(0xFF6347FF) }, + { "TRANSPARENT", new Color(0xFFFFFF00) }, + { "TURQUOISE", new Color(0x40E0D0FF) }, + { "VIOLET", new Color(0xEE82EEFF) }, + { "WEB_GRAY", new Color(0x808080FF) }, + { "WEB_GREEN", new Color(0x008000FF) }, + { "WEB_MAROON", new Color(0x800000FF) }, + { "WEB_PURPLE", new Color(0x800080FF) }, + { "WHEAT", new Color(0xF5DEB3FF) }, + { "WHITE", new Color(0xFFFFFFFF) }, + { "WHITE_SMOKE", new Color(0xF5F5F5FF) }, + { "YELLOW", new Color(0xFFFF00FF) }, + { "YELLOW_GREEN", new Color(0x9ACD32FF) }, }; #pragma warning disable CS1591 // Disable warning: "Missing XML comment for publicly visible type or member" diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs new file mode 100644 index 0000000000..42f19ace1a --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs @@ -0,0 +1,98 @@ +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using Godot.Bridge; + +namespace Godot; + +/// <summary> +/// Provides a GCHandle that becomes weak when unloading the assembly load context, without having +/// to manually replace the GCHandle. This hides all the complexity of releasing strong GC handles +/// to allow the assembly load context to unload properly. +/// +/// Internally, a strong CustomGCHandle actually contains a weak GCHandle, while the actual strong +/// reference is stored in a static table. +/// </summary> +public static class CustomGCHandle +{ + // ConditionalWeakTable uses DependentHandle, so it stores weak references. + // Having the assembly load context as key won't prevent it from unloading. + private static ConditionalWeakTable<AssemblyLoadContext, object?> _alcsBeingUnloaded = new(); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool IsAlcBeingUnloaded(AssemblyLoadContext alc) => _alcsBeingUnloaded.TryGetValue(alc, out _); + + // ReSharper disable once RedundantNameQualifier + private static ConcurrentDictionary< + AssemblyLoadContext, + ConcurrentDictionary<GCHandle, object> + > _strongReferencesByAlc = new(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void OnAlcUnloading(AssemblyLoadContext alc) + { + _alcsBeingUnloaded.Add(alc, null); + + if (_strongReferencesByAlc.TryRemove(alc, out var strongReferences)) + { + strongReferences.Clear(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GCHandle AllocStrong(object value) + => AllocStrong(value, value.GetType()); + + public static GCHandle AllocStrong(object value, Type valueType) + { + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + var alc = AssemblyLoadContext.GetLoadContext(valueType.Assembly); + + if (alc != null) + { + var weakHandle = GCHandle.Alloc(value, GCHandleType.Weak); + + if (!IsAlcBeingUnloaded(alc)) + { + var strongReferences = _strongReferencesByAlc.GetOrAdd(alc, + static alc => + { + alc.Unloading += OnAlcUnloading; + return new(); + }); + strongReferences.TryAdd(weakHandle, value); + } + + return weakHandle; + } + } + + return GCHandle.Alloc(value, GCHandleType.Normal); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GCHandle AllocWeak(object value) => GCHandle.Alloc(value, GCHandleType.Weak); + + public static void Free(GCHandle handle) + { + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + var target = handle.Target; + + if (target != null) + { + var alc = AssemblyLoadContext.GetLoadContext(target.GetType().Assembly); + + if (alc != null && _strongReferencesByAlc.TryGetValue(alc, out var strongReferences)) + _ = strongReferences.TryRemove(handle, out _); + } + } + + handle.Free(); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs index edfe3464ec..6fbc04702a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs @@ -1,13 +1,18 @@ using System; using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; +using Godot.NativeInterop; + +#nullable enable namespace Godot { internal static class DebuggingUtils { - internal static void AppendTypeName(this StringBuilder sb, Type type) + private static void AppendTypeName(this StringBuilder sb, Type type) { if (type.IsPrimitive) sb.Append(type.Name); @@ -16,21 +21,97 @@ namespace Godot else sb.Append(type); - sb.Append(" "); + sb.Append(' '); } - public static void InstallTraceListener() + internal static void InstallTraceListener() { Trace.Listeners.Clear(); Trace.Listeners.Add(new GodotTraceListener()); } - public static void GetStackFrameInfo(StackFrame frame, out string fileName, out int fileLineNumber, out string methodDecl) + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal ref struct godot_stack_info + { + public godot_string File; + public godot_string Func; + public int Line; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal ref struct godot_stack_info_vector { - fileName = frame.GetFileName(); - fileLineNumber = frame.GetFileLineNumber(); + private IntPtr _writeProxy; + private unsafe godot_stack_info* _ptr; + + public readonly unsafe godot_stack_info* Elements + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public void Resize(int size) + { + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size)); + var err = NativeFuncs.godotsharp_stack_info_vector_resize(ref this, size); + if (err != Error.Ok) + throw new InvalidOperationException("Failed to resize vector. Error code is: " + err.ToString()); + } + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_stack_info_vector_destroy(ref this); + _ptr = null; + } + } - MethodBase methodBase = frame.GetMethod(); + [UnmanagedCallersOnly] + internal static unsafe void GetCurrentStackInfo(void* destVector) + { + try + { + var vector = (godot_stack_info_vector*)destVector; + var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true); + int frameCount = stackTrace.FrameCount; + + if (frameCount == 0) + return; + + vector->Resize(frameCount); + + int i = 0; + foreach (StackFrame frame in stackTrace.GetFrames()) + { + string? fileName = frame.GetFileName(); + int fileLineNumber = frame.GetFileLineNumber(); + + GetStackFrameMethodDecl(frame, out string methodDecl); + + godot_stack_info* stackInfo = &vector->Elements[i]; + + // Assign directly to element in Vector. This way we don't need to worry + // about disposal if an exception is thrown. The Vector takes care of it. + stackInfo->File = Marshaling.ConvertStringToNative(fileName); + stackInfo->Func = Marshaling.ConvertStringToNative(methodDecl); + stackInfo->Line = fileLineNumber; + + i++; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + internal static void GetStackFrameMethodDecl(StackFrame frame, out string methodDecl) + { + MethodBase? methodBase = frame.GetMethod(); if (methodBase == null) { @@ -43,15 +124,15 @@ namespace Godot if (methodBase is MethodInfo) sb.AppendTypeName(((MethodInfo)methodBase).ReturnType); - sb.Append(methodBase.DeclaringType.FullName); - sb.Append("."); + sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>"); + sb.Append('.'); sb.Append(methodBase.Name); if (methodBase.IsGenericMethod) { Type[] genericParams = methodBase.GetGenericArguments(); - sb.Append("<"); + sb.Append('<'); for (int j = 0; j < genericParams.Length; j++) { @@ -61,10 +142,10 @@ namespace Godot sb.AppendTypeName(genericParams[j]); } - sb.Append(">"); + sb.Append('>'); } - sb.Append("("); + sb.Append('('); bool varArgs = (methodBase.CallingConvention & CallingConventions.VarArgs) != 0; @@ -81,7 +162,7 @@ namespace Godot sb.AppendTypeName(parameter[i].ParameterType); } - sb.Append(")"); + sb.Append(')'); methodDecl = sb.ToString(); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 1dca9e6ea7..266038a0af 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -1,14 +1,72 @@ +#nullable enable + using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { internal static class DelegateUtils { + [UnmanagedCallersOnly] + internal static godot_bool DelegateEquals(IntPtr delegateGCHandleA, IntPtr delegateGCHandleB) + { + try + { + var @delegateA = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleA).Target; + var @delegateB = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleB).Target; + return (@delegateA! == @delegateB!).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, godot_variant** args, uint argc, + godot_variant* outRet) + { + try + { + // TODO: Optimize + var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!; + var managedArgs = new object?[argc]; + + var parameterInfos = @delegate.Method.GetParameters(); + var paramsLength = parameterInfos.Length; + + if (argc != paramsLength) + { + throw new InvalidOperationException( + $"The delegate expects {paramsLength} arguments, but received {argc}."); + } + + for (uint i = 0; i < argc; i++) + { + managedArgs[i] = Marshaling.ConvertVariantToManagedObjectOfType( + *args[i], parameterInfos[i].ParameterType); + } + + object? invokeRet = @delegate.DynamicInvoke(managedArgs); + + *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRet = default; + } + } + + // TODO: Check if we should be using BindingFlags.DeclaredOnly (would give better reflection performance). + private enum TargetKind : uint { Static, @@ -39,20 +97,20 @@ namespace Godot } } - if (TrySerializeSingleDelegate(@delegate, out byte[] buffer)) + if (TrySerializeSingleDelegate(@delegate, out byte[]? buffer)) { - serializedData.Add(buffer); + serializedData.Add((Span<byte>)buffer); return true; } return false; } - private static bool TrySerializeSingleDelegate(Delegate @delegate, out byte[] buffer) + private static bool TrySerializeSingleDelegate(Delegate @delegate, [MaybeNullWhen(false)] out byte[] buffer) { buffer = null; - object target = @delegate.Target; + object? target = @delegate.Target; switch (target) { @@ -72,12 +130,14 @@ namespace Godot return true; } } + // ReSharper disable once RedundantNameQualifier case Godot.Object godotObject: { using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { writer.Write((ulong)TargetKind.GodotObject); + // ReSharper disable once RedundantCast writer.Write((ulong)godotObject.GetInstanceId()); SerializeType(writer, @delegate.GetType()); @@ -93,7 +153,7 @@ namespace Godot { Type targetType = target.GetType(); - if (targetType.GetCustomAttribute(typeof(CompilerGeneratedAttribute), true) != null) + if (targetType.IsDefined(typeof(CompilerGeneratedAttribute), true)) { // Compiler generated. Probably a closure. Try to serialize it. @@ -121,8 +181,18 @@ namespace Godot if (variantType == Variant.Type.Nil) return false; + static byte[] Var2Bytes(in godot_variant var) + { + NativeFuncs.godotsharp_var2bytes(var, false.ToGodotBool(), out var varBytes); + using (varBytes) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(varBytes); + } + writer.Write(field.Name); - byte[] valueBuffer = GD.Var2Bytes(field.GetValue(target)); + + var fieldValue = field.GetValue(target); + using var fieldValueVariant = Marshaling.ConvertManagedObjectToVariant(fieldValue); + byte[] valueBuffer = Var2Bytes(fieldValueVariant); writer.Write(valueBuffer.Length); writer.Write(valueBuffer); } @@ -139,9 +209,6 @@ namespace Godot private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo) { - if (methodInfo == null) - return false; - SerializeType(writer, methodInfo.DeclaringType); writer.Write(methodInfo.Name); @@ -180,7 +247,7 @@ namespace Godot return true; } - private static void SerializeType(BinaryWriter writer, Type type) + private static void SerializeType(BinaryWriter writer, Type? type) { if (type == null) { @@ -195,9 +262,8 @@ namespace Godot int genericArgumentsCount = genericArgs.Length; writer.Write(genericArgumentsCount); - string assemblyQualifiedName = genericTypeDef.AssemblyQualifiedName; - Debug.Assert(assemblyQualifiedName != null); - writer.Write(assemblyQualifiedName); + writer.Write(genericTypeDef.Assembly.GetName().Name ?? ""); + writer.Write(genericTypeDef.FullName ?? genericTypeDef.ToString()); for (int i = 0; i < genericArgs.Length; i++) SerializeType(writer, genericArgs[i]); @@ -207,17 +273,71 @@ namespace Godot int genericArgumentsCount = 0; writer.Write(genericArgumentsCount); - string assemblyQualifiedName = type.AssemblyQualifiedName; - Debug.Assert(assemblyQualifiedName != null); - writer.Write(assemblyQualifiedName); + writer.Write(type.Assembly.GetName().Name ?? ""); + writer.Write(type.FullName ?? type.ToString()); + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool TrySerializeDelegateWithGCHandle(IntPtr delegateGCHandle, + godot_array* nSerializedData) + { + try + { + var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(*nSerializedData)); + + var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!; + + return TrySerializeDelegate(@delegate, serializedData) + .ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; } } - private static bool TryDeserializeDelegate(Collections.Array serializedData, out Delegate @delegate) + [UnmanagedCallersOnly] + internal static unsafe godot_bool TryDeserializeDelegateWithGCHandle(godot_array* nSerializedData, + IntPtr* delegateGCHandle) { + try + { + var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(*nSerializedData)); + + if (TryDeserializeDelegate(serializedData, out Delegate? @delegate)) + { + *delegateGCHandle = GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(@delegate)); + return godot_bool.True; + } + else + { + *delegateGCHandle = IntPtr.Zero; + return godot_bool.False; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *delegateGCHandle = default; + return godot_bool.False; + } + } + + internal static bool TryDeserializeDelegate(Collections.Array serializedData, + [MaybeNullWhen(false)] out Delegate @delegate) + { + @delegate = null; + if (serializedData.Count == 1) { - object elem = serializedData[0]; + var elem = serializedData[0].Obj; + + if (elem == null) + return false; if (elem is Collections.Array multiCastData) return TryDeserializeDelegate(multiCastData, out @delegate); @@ -225,20 +345,23 @@ namespace Godot return TryDeserializeSingleDelegate((byte[])elem, out @delegate); } - @delegate = null; - var delegates = new List<Delegate>(serializedData.Count); - foreach (object elem in serializedData) + foreach (Variant variantElem in serializedData) { + var elem = variantElem.Obj; + + if (elem == null) + continue; + if (elem is Collections.Array multiCastData) { - if (TryDeserializeDelegate(multiCastData, out Delegate oneDelegate)) + if (TryDeserializeDelegate(multiCastData, out Delegate? oneDelegate)) delegates.Add(oneDelegate); } else { - if (TryDeserializeSingleDelegate((byte[])elem, out Delegate oneDelegate)) + if (TryDeserializeSingleDelegate((byte[])elem, out Delegate? oneDelegate)) delegates.Add(oneDelegate); } } @@ -246,11 +369,11 @@ namespace Godot if (delegates.Count <= 0) return false; - @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray()); + @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray())!; return true; } - private static bool TryDeserializeSingleDelegate(byte[] buffer, out Delegate @delegate) + private static bool TryDeserializeSingleDelegate(byte[] buffer, [MaybeNullWhen(false)] out Delegate @delegate) { @delegate = null; @@ -263,49 +386,59 @@ namespace Godot { case TargetKind.Static: { - Type delegateType = DeserializeType(reader); + Type? delegateType = DeserializeType(reader); if (delegateType == null) return false; - if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo)) + return false; + + @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo, throwOnBindFailure: false); + + if (@delegate == null) return false; - @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo); return true; } case TargetKind.GodotObject: { ulong objectId = reader.ReadUInt64(); + // ReSharper disable once RedundantNameQualifier Godot.Object godotObject = GD.InstanceFromId(objectId); if (godotObject == null) return false; - Type delegateType = DeserializeType(reader); + Type? delegateType = DeserializeType(reader); if (delegateType == null) return false; - if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo)) + return false; + + @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo, + throwOnBindFailure: false); + + if (@delegate == null) return false; - @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo); return true; } case TargetKind.CompilerGenerated: { - Type targetType = DeserializeType(reader); + Type? targetType = DeserializeType(reader); if (targetType == null) return false; - Type delegateType = DeserializeType(reader); + Type? delegateType = DeserializeType(reader); if (delegateType == null) return false; - if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo)) return false; int fieldCount = reader.ReadInt32(); - object recreatedTarget = Activator.CreateInstance(targetType); + object recreatedTarget = Activator.CreateInstance(targetType)!; for (int i = 0; i < fieldCount; i++) { @@ -313,11 +446,17 @@ namespace Godot int valueBufferLength = reader.ReadInt32(); byte[] valueBuffer = reader.ReadBytes(valueBufferLength); - FieldInfo fieldInfo = targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public); + FieldInfo? fieldInfo = targetType.GetField(name, + BindingFlags.Instance | BindingFlags.Public); fieldInfo?.SetValue(recreatedTarget, GD.Bytes2Var(valueBuffer)); } - @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo); + @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo, + throwOnBindFailure: false); + + if (@delegate == null) + return false; + return true; } default: @@ -326,18 +465,22 @@ namespace Godot } } - private static bool TryDeserializeMethodInfo(BinaryReader reader, out MethodInfo methodInfo) + private static bool TryDeserializeMethodInfo(BinaryReader reader, + [MaybeNullWhen(false)] out MethodInfo methodInfo) { methodInfo = null; - Type declaringType = DeserializeType(reader); + Type? declaringType = DeserializeType(reader); + + if (declaringType == null) + return false; string methodName = reader.ReadString(); int flags = reader.ReadInt32(); bool hasReturn = reader.ReadBoolean(); - Type returnType = hasReturn ? DeserializeType(reader) : typeof(void); + Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void); int parametersCount = reader.ReadInt32(); @@ -347,7 +490,7 @@ namespace Godot for (int i = 0; i < parametersCount; i++) { - Type parameterType = DeserializeType(reader); + Type? parameterType = DeserializeType(reader); if (parameterType == null) return false; parameterTypes[i] = parameterType; @@ -361,15 +504,23 @@ namespace Godot return methodInfo != null && methodInfo.ReturnType == returnType; } - private static Type DeserializeType(BinaryReader reader) + private static Type? DeserializeType(BinaryReader reader) { int genericArgumentsCount = reader.ReadInt32(); if (genericArgumentsCount == -1) return null; - string assemblyQualifiedName = reader.ReadString(); - var type = Type.GetType(assemblyQualifiedName); + string assemblyName = reader.ReadString(); + + if (assemblyName.Length == 0) + { + GD.PushError($"Missing assembly name of type when attempting to deserialize delegate"); + return null; + } + + string typeFullName = reader.ReadString(); + var type = ReflectionUtils.FindTypeInLoadedAssemblies(assemblyName, typeFullName); if (type == null) return null; // Type not found @@ -380,7 +531,7 @@ namespace Godot for (int i = 0; i < genericArgumentsCount; i++) { - Type genericArgumentType = DeserializeType(reader); + Type? genericArgumentType = DeserializeType(reader); if (genericArgumentType == null) return null; genericArgumentTypes[i] = genericArgumentType; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index e80b6af68f..95ad097192 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -1,79 +1,50 @@ using System; using System.Collections.Generic; using System.Collections; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot.Collections { - internal class DictionarySafeHandle : SafeHandle - { - public DictionarySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) - { - this.handle = handle; - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero; } - } - - protected override bool ReleaseHandle() - { - Dictionary.godot_icall_Dictionary_Dtor(handle); - return true; - } - } - /// <summary> /// Wrapper around Godot's Dictionary class, a dictionary of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. /// </summary> - public class Dictionary : IDictionary, IDisposable + public sealed class Dictionary : + IDictionary<Variant, Variant>, + IReadOnlyDictionary<Variant, Variant>, + IDisposable { - private DictionarySafeHandle _safeHandle; - private bool _disposed = false; + internal godot_dictionary.movable NativeValue; + + private WeakReference<IDisposable> _weakReferenceToSelf; /// <summary> /// Constructs a new empty <see cref="Dictionary"/>. /// </summary> public Dictionary() { - _safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); + NativeValue = (godot_dictionary.movable)NativeFuncs.godotsharp_dictionary_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } - /// <summary> - /// Constructs a new <see cref="Dictionary"/> from the given dictionary's elements. - /// </summary> - /// <param name="dictionary">The dictionary to construct from.</param> - /// <returns>A new Godot Dictionary.</returns> - public Dictionary(IDictionary dictionary) : this() - { - if (dictionary == null) - throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); - - foreach (DictionaryEntry entry in dictionary) - Add(entry.Key, entry.Value); - } - - internal Dictionary(DictionarySafeHandle handle) + private Dictionary(godot_dictionary nativeValueToOwn) { - _safeHandle = handle; + NativeValue = (godot_dictionary.movable)(nativeValueToOwn.IsAllocated ? + nativeValueToOwn : + NativeFuncs.godotsharp_dictionary_new()); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } - internal Dictionary(IntPtr handle) - { - _safeHandle = new DictionarySafeHandle(handle); - } + // Explicit name to make it very clear + internal static Dictionary CreateTakingOwnershipOfDisposableValue(godot_dictionary nativeValueToOwn) + => new Dictionary(nativeValueToOwn); - internal IntPtr GetPtr() + ~Dictionary() { - if (_disposed) - throw new ObjectDisposedException(GetType().FullName); - - return _safeHandle.DangerousGetHandle(); + Dispose(false); } /// <summary> @@ -81,16 +52,19 @@ namespace Godot.Collections /// </summary> public void Dispose() { - if (_disposed) - return; + Dispose(true); + GC.SuppressFinalize(this); + } - if (_safeHandle != null) + public void Dispose(bool disposing) + { + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) { - _safeHandle.Dispose(); - _safeHandle = null; + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); } - - _disposed = true; } /// <summary> @@ -100,7 +74,10 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary Duplicate(bool deep = false) { - return new Dictionary(godot_icall_Dictionary_Duplicate(GetPtr(), deep)); + godot_dictionary newDictionary; + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_duplicate(ref self, deep.ToGodotBool(), out newDictionary); + return CreateTakingOwnershipOfDisposableValue(newDictionary); } // IDictionary @@ -108,176 +85,250 @@ namespace Godot.Collections /// <summary> /// Gets the collection of keys in this <see cref="Dictionary"/>. /// </summary> - public ICollection Keys + public ICollection<Variant> Keys { get { - IntPtr handle = godot_icall_Dictionary_Keys(GetPtr()); - return new Array(new ArraySafeHandle(handle)); + godot_array keysArray; + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_keys(ref self, out keysArray); + return Array.CreateTakingOwnershipOfDisposableValue(keysArray); } } /// <summary> /// Gets the collection of elements in this <see cref="Dictionary"/>. /// </summary> - public ICollection Values + public ICollection<Variant> Values { get { - IntPtr handle = godot_icall_Dictionary_Values(GetPtr()); - return new Array(new ArraySafeHandle(handle)); + godot_array valuesArray; + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray); + return Array.CreateTakingOwnershipOfDisposableValue(valuesArray); } } + IEnumerable<Variant> IReadOnlyDictionary<Variant, Variant>.Keys => Keys; + + IEnumerable<Variant> IReadOnlyDictionary<Variant, Variant>.Values => Values; + private (Array keys, Array values, int count) GetKeyValuePairs() { - int count = godot_icall_Dictionary_KeyValuePairs(GetPtr(), out IntPtr keysHandle, out IntPtr valuesHandle); - Array keys = new Array(new ArraySafeHandle(keysHandle)); - Array values = new Array(new ArraySafeHandle(valuesHandle)); - return (keys, values, count); - } + var self = (godot_dictionary)NativeValue; - bool IDictionary.IsFixedSize => false; + godot_array keysArray; + NativeFuncs.godotsharp_dictionary_keys(ref self, out keysArray); + var keys = Array.CreateTakingOwnershipOfDisposableValue(keysArray); - bool IDictionary.IsReadOnly => false; + godot_array valuesArray; + NativeFuncs.godotsharp_dictionary_keys(ref self, out valuesArray); + var values = Array.CreateTakingOwnershipOfDisposableValue(valuesArray); + + int count = NativeFuncs.godotsharp_dictionary_count(ref self); + + return (keys, values, count); + } /// <summary> - /// Returns the object at the given <paramref name="key"/>. + /// Returns the value at the given <paramref name="key"/>. /// </summary> - /// <value>The object at the given <paramref name="key"/>.</value> - public object this[object key] + /// <value>The value at the given <paramref name="key"/>.</value> + public Variant this[Variant key] { - get => godot_icall_Dictionary_GetValue(GetPtr(), key); - set => godot_icall_Dictionary_SetValue(GetPtr(), key, value); + get + { + var self = (godot_dictionary)NativeValue; + + if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + (godot_variant)key.NativeVar, out godot_variant value).ToBool()) + { + return Variant.CreateTakingOwnershipOfDisposableValue(value); + } + else + { + throw new KeyNotFoundException(); + } + } + set + { + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_set_value(ref self, + (godot_variant)key.NativeVar, (godot_variant)value.NativeVar); + } } /// <summary> - /// Adds an object <paramref name="value"/> at key <paramref name="key"/> + /// Adds an value <paramref name="value"/> at key <paramref name="key"/> /// to this <see cref="Dictionary"/>. /// </summary> - /// <param name="key">The key at which to add the object.</param> - /// <param name="value">The object to add.</param> - public void Add(object key, object value) => godot_icall_Dictionary_Add(GetPtr(), key, value); + /// <param name="key">The key at which to add the value.</param> + /// <param name="value">The value to add.</param> + public void Add(Variant key, Variant value) + { + var variantKey = (godot_variant)key.NativeVar; + var self = (godot_dictionary)NativeValue; + + if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) + throw new ArgumentException("An element with the same key already exists", nameof(key)); + + godot_variant variantValue = (godot_variant)value.NativeVar; + NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); + } + + void ICollection<KeyValuePair<Variant, Variant>>.Add(KeyValuePair<Variant, Variant> item) + => Add(item.Key, item.Value); /// <summary> /// Erases all items from this <see cref="Dictionary"/>. /// </summary> - public void Clear() => godot_icall_Dictionary_Clear(GetPtr()); + public void Clear() + { + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_clear(ref self); + } /// <summary> /// Checks if this <see cref="Dictionary"/> contains the given key. /// </summary> /// <param name="key">The key to look for.</param> /// <returns>Whether or not this dictionary contains the given key.</returns> - public bool Contains(object key) => godot_icall_Dictionary_ContainsKey(GetPtr(), key); + public bool ContainsKey(Variant key) + { + var self = (godot_dictionary)NativeValue; + return NativeFuncs.godotsharp_dictionary_contains_key(ref self, (godot_variant)key.NativeVar).ToBool(); + } - /// <summary> - /// Gets an enumerator for this <see cref="Dictionary"/>. - /// </summary> - /// <returns>An enumerator.</returns> - public IDictionaryEnumerator GetEnumerator() => new DictionaryEnumerator(this); + public bool Contains(KeyValuePair<Variant, Variant> item) + { + godot_variant variantKey = (godot_variant)item.Key.NativeVar; + var self = (godot_dictionary)NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; + + godot_variant variantValue = (godot_variant)item.Value.NativeVar; + return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); + } + } /// <summary> /// Removes an element from this <see cref="Dictionary"/> by key. /// </summary> /// <param name="key">The key of the element to remove.</param> - public void Remove(object key) => godot_icall_Dictionary_RemoveKey(GetPtr(), key); + public bool Remove(Variant key) + { + var self = (godot_dictionary)NativeValue; + return NativeFuncs.godotsharp_dictionary_remove_key(ref self, (godot_variant)key.NativeVar).ToBool(); + } - // ICollection + public bool Remove(KeyValuePair<Variant, Variant> item) + { + godot_variant variantKey = (godot_variant)item.Key.NativeVar; + var self = (godot_dictionary)NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; - object ICollection.SyncRoot => this; + godot_variant variantValue = (godot_variant)item.Value.NativeVar; + if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) + { + return NativeFuncs.godotsharp_dictionary_remove_key( + ref self, variantKey).ToBool(); + } - bool ICollection.IsSynchronized => false; + return false; + } + } /// <summary> /// Returns the number of elements in this <see cref="Dictionary"/>. /// This is also known as the size or length of the dictionary. /// </summary> /// <returns>The number of elements.</returns> - public int Count => godot_icall_Dictionary_Count(GetPtr()); + public int Count + { + get + { + var self = (godot_dictionary)NativeValue; + return NativeFuncs.godotsharp_dictionary_count(ref self); + } + } + + bool ICollection<KeyValuePair<Variant, Variant>>.IsReadOnly => false; + + public bool TryGetValue(Variant key, out Variant value) + { + var self = (godot_dictionary)NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + (godot_variant)key.NativeVar, out godot_variant retValue).ToBool(); + + value = found ? Variant.CreateTakingOwnershipOfDisposableValue(retValue) : default; + + return found; + } /// <summary> - /// Copies the elements of this <see cref="Dictionary"/> to the given - /// untyped C# array, starting at the given index. + /// Copies the elements of this <see cref="Dictionary"/> to the given untyped + /// <see cref="KeyValuePair{TKey, TValue}"/> array, starting at the given index. /// </summary> /// <param name="array">The array to copy to.</param> - /// <param name="index">The index to start at.</param> - public void CopyTo(System.Array array, int index) + /// <param name="arrayIndex">The index to start at.</param> + public void CopyTo(KeyValuePair<Variant, Variant>[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension."); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); var (keys, values, count) = GetKeyValuePairs(); - if (array.Length < (index + count)) - throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + if (array.Length < (arrayIndex + count)) + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); for (int i = 0; i < count; i++) { - array.SetValue(new DictionaryEntry(keys[i], values[i]), index); - index++; + array[arrayIndex] = new(keys[i], values[i]); + arrayIndex++; } } // IEnumerable - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private class DictionaryEnumerator : IDictionaryEnumerator + /// <summary> + /// Gets an enumerator for this <see cref="Dictionary"/>. + /// </summary> + /// <returns>An enumerator.</returns> + public IEnumerator<KeyValuePair<Variant, Variant>> GetEnumerator() { - private readonly Dictionary _dictionary; - private readonly int _count; - private int _index = -1; - private bool _dirty = true; - - private DictionaryEntry _entry; - - public DictionaryEnumerator(Dictionary dictionary) - { - _dictionary = dictionary; - _count = dictionary.Count; - } - - public object Current => Entry; - - public DictionaryEntry Entry - { - get - { - if (_dirty) - { - UpdateEntry(); - } - return _entry; - } - } - - private void UpdateEntry() + for (int i = 0; i < Count; i++) { - _dirty = false; - godot_icall_Dictionary_KeyValuePairAt(_dictionary.GetPtr(), _index, out object key, out object value); - _entry = new DictionaryEntry(key, value); + yield return GetKeyValuePair(i); } + } - public object Key => Entry.Key; - - public object Value => Entry.Value; - - public bool MoveNext() - { - _index++; - _dirty = true; - return _index < _count; - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public void Reset() - { - _index = -1; - _dirty = true; - } + private KeyValuePair<Variant, Variant> GetKeyValuePair(int index) + { + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index, + out godot_variant key, + out godot_variant value); + return new KeyValuePair<Variant, Variant>(Variant.CreateTakingOwnershipOfDisposableValue(key), + Variant.CreateTakingOwnershipOfDisposableValue(value)); } /// <summary> @@ -286,74 +337,11 @@ namespace Godot.Collections /// <returns>A string representation of this dictionary.</returns> public override string ToString() { - return godot_icall_Dictionary_ToString(GetPtr()); + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_to_string(ref self, out godot_string str); + using (str) + return Marshaling.ConvertStringToManaged(str); } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Ctor(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Values(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Dictionary_Count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Dictionary_KeyValuePairs(IntPtr ptr, out IntPtr keys, out IntPtr values); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_KeyValuePairAt(IntPtr ptr, int index, out object key, out object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_KeyValuePairAt_Generic(IntPtr ptr, int index, out object key, out object value, int valueTypeEncoding, IntPtr valueTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Clear(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_Dictionary_ToString(IntPtr ptr); } /// <summary> @@ -364,16 +352,51 @@ namespace Godot.Collections /// </summary> /// <typeparam name="TKey">The type of the dictionary's keys.</typeparam> /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> - public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue> + public class Dictionary<[MustBeVariant] TKey, [MustBeVariant] TValue> : + IDictionary<TKey, TValue>, + IReadOnlyDictionary<TKey, TValue> { - private readonly Dictionary _objectDict; + // ReSharper disable StaticMemberInGenericType + // Warning is about unique static fields being created for each generic type combination: + // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html + // In our case this is exactly what we want. + + private static unsafe delegate* managed<in TKey, godot_variant> _convertKeyToVariantCallback; + private static unsafe delegate* managed<in godot_variant, TKey> _convertKeyToManagedCallback; + private static unsafe delegate* managed<in TValue, godot_variant> _convertValueToVariantCallback; + private static unsafe delegate* managed<in godot_variant, TValue> _convertValueToManagedCallback; + + // ReSharper restore StaticMemberInGenericType + + static unsafe Dictionary() + { + _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); + _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); + _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); + _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); + } + + private static unsafe void ValidateVariantConversionCallbacks() + { + if (_convertKeyToVariantCallback == null || _convertKeyToManagedCallback == null) + { + throw new InvalidOperationException( + $"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'"); + } + + if (_convertValueToVariantCallback == null || _convertValueToManagedCallback == null) + { + throw new InvalidOperationException( + $"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'"); + } + } - internal static int valTypeEncoding; - internal static IntPtr valTypeClass; + private readonly Dictionary _underlyingDict; - static Dictionary() + internal ref godot_dictionary.movable NativeValue { - Dictionary.godot_icall_Dictionary_Generic_GetValueTypeInfo(typeof(TValue), out valTypeEncoding, out valTypeClass); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _underlyingDict.NativeValue; } /// <summary> @@ -381,7 +404,9 @@ namespace Godot.Collections /// </summary> public Dictionary() { - _objectDict = new Dictionary(); + ValidateVariantConversionCallbacks(); + + _underlyingDict = new Dictionary(); } /// <summary> @@ -391,19 +416,15 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) { - _objectDict = new Dictionary(); + ValidateVariantConversionCallbacks(); if (dictionary == null) - throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); - - // TODO: Can be optimized + throw new ArgumentNullException(nameof(dictionary)); - IntPtr godotDictionaryPtr = GetPtr(); + _underlyingDict = new Dictionary(); foreach (KeyValuePair<TKey, TValue> entry in dictionary) - { - Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, entry.Key, entry.Value); - } + Add(entry.Key, entry.Value); } /// <summary> @@ -413,18 +434,15 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { - _objectDict = dictionary; - } + ValidateVariantConversionCallbacks(); - internal Dictionary(IntPtr handle) - { - _objectDict = new Dictionary(handle); + _underlyingDict = dictionary; } - internal Dictionary(DictionarySafeHandle handle) - { - _objectDict = new Dictionary(handle); - } + // Explicit name to make it very clear + internal static Dictionary<TKey, TValue> CreateTakingOwnershipOfDisposableValue( + godot_dictionary nativeValueToOwn) + => new Dictionary<TKey, TValue>(Dictionary.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); /// <summary> /// Converts this typed <see cref="Dictionary{TKey, TValue}"/> to an untyped <see cref="Dictionary"/>. @@ -432,12 +450,7 @@ namespace Godot.Collections /// <param name="from">The typed dictionary to convert.</param> public static explicit operator Dictionary(Dictionary<TKey, TValue> from) { - return from._objectDict; - } - - internal IntPtr GetPtr() - { - return _objectDict.GetPtr(); + return from?._underlyingDict; } /// <summary> @@ -447,7 +460,7 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary<TKey, TValue> Duplicate(bool deep = false) { - return new Dictionary<TKey, TValue>(_objectDict.Duplicate(deep)); + return new Dictionary<TKey, TValue>(_underlyingDict.Duplicate(deep)); } // IDictionary<TKey, TValue> @@ -456,10 +469,32 @@ namespace Godot.Collections /// Returns the value at the given <paramref name="key"/>. /// </summary> /// <value>The value at the given <paramref name="key"/>.</value> - public TValue this[TKey key] + public unsafe TValue this[TKey key] { - get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(_objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); } - set { _objectDict[key] = value; } + get + { + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + + if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant value).ToBool()) + { + using (value) + return _convertValueToManagedCallback(value); + } + else + { + throw new KeyNotFoundException(); + } + } + set + { + using var variantKey = _convertKeyToVariantCallback(key); + using var variantValue = _convertValueToVariantCallback(value); + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_set_value(ref self, + variantKey, variantValue); + } } /// <summary> @@ -469,8 +504,10 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(_objectDict.GetPtr()); - return new Array<TKey>(new ArraySafeHandle(handle)); + godot_array keyArray; + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_keys(ref self, out keyArray); + return Array<TKey>.CreateTakingOwnershipOfDisposableValue(keyArray); } } @@ -481,15 +518,30 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Values(_objectDict.GetPtr()); - return new Array<TValue>(new ArraySafeHandle(handle)); + godot_array valuesArray; + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray); + return Array<TValue>.CreateTakingOwnershipOfDisposableValue(valuesArray); } } - private KeyValuePair<TKey, TValue> GetKeyValuePair(int index) + IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys; + + IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values; + + private unsafe KeyValuePair<TKey, TValue> GetKeyValuePair(int index) { - Dictionary.godot_icall_Dictionary_KeyValuePairAt_Generic(GetPtr(), index, out object key, out object value, valTypeEncoding, valTypeClass); - return new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value); + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index, + out godot_variant key, + out godot_variant value); + using (key) + using (value) + { + return new KeyValuePair<TKey, TValue>( + _convertKeyToManagedCallback(key), + _convertValueToManagedCallback(value)); + } } /// <summary> @@ -498,9 +550,16 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key at which to add the object.</param> /// <param name="value">The object to add.</param> - public void Add(TKey key, TValue value) + public unsafe void Add(TKey key, TValue value) { - _objectDict.Add(key, value); + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + + if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) + throw new ArgumentException("An element with the same key already exists", nameof(key)); + + using var variantValue = _convertValueToVariantCallback(value); + NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); } /// <summary> @@ -508,18 +567,22 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key to look for.</param> /// <returns>Whether or not this dictionary contains the given key.</returns> - public bool ContainsKey(TKey key) + public unsafe bool ContainsKey(TKey key) { - return _objectDict.Contains(key); + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool(); } /// <summary> /// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key. /// </summary> /// <param name="key">The key of the element to remove.</param> - public bool Remove(TKey key) + public unsafe bool Remove(TKey key) { - return Dictionary.godot_icall_Dictionary_RemoveKey(GetPtr(), key); + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); } /// <summary> @@ -528,10 +591,16 @@ namespace Godot.Collections /// <param name="key">The key of the element to get.</param> /// <param name="value">The value at the given <paramref name="key"/>.</param> /// <returns>If an object was found for the given <paramref name="key"/>.</returns> - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - bool found = Dictionary.godot_icall_Dictionary_TryGetValue_Generic(GetPtr(), key, out object retValue, valTypeEncoding, valTypeClass); - value = found ? (TValue)retValue : default; + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + value = found ? _convertValueToManagedCallback(retValue) : default; + return found; } @@ -542,29 +611,33 @@ namespace Godot.Collections /// This is also known as the size or length of the dictionary. /// </summary> /// <returns>The number of elements.</returns> - public int Count - { - get { return _objectDict.Count; } - } + public int Count => _underlyingDict.Count; bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) - { - _objectDict.Add(item.Key, item.Value); - } + => Add(item.Key, item.Value); /// <summary> /// Erases all the items from this <see cref="Dictionary{TKey, TValue}"/>. /// </summary> - public void Clear() - { - _objectDict.Clear(); - } + public void Clear() => _underlyingDict.Clear(); - bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) + unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { - return _objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); + using var variantKey = _convertKeyToVariantCallback(item.Key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; + + using var variantValue = _convertValueToVariantCallback(item.Value); + return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); + } } /// <summary> @@ -579,12 +652,14 @@ namespace Godot.Collections throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); int count = Count; if (array.Length < (arrayIndex + count)) - throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); for (int i = 0; i < count; i++) { @@ -593,10 +668,27 @@ namespace Godot.Collections } } - bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) + unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { - return Dictionary.godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value); - ; + using var variantKey = _convertKeyToVariantCallback(item.Key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; + + using var variantValue = _convertValueToVariantCallback(item.Value); + if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) + { + return NativeFuncs.godotsharp_dictionary_remove_key( + ref self, variantKey).ToBool(); + } + + return false; + } } // IEnumerable<KeyValuePair<TKey, TValue>> @@ -613,15 +705,18 @@ namespace Godot.Collections } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// <summary> /// Converts this <see cref="Dictionary{TKey, TValue}"/> to a string. /// </summary> /// <returns>A string representation of this dictionary.</returns> - public override string ToString() => _objectDict.ToString(); + public override string ToString() => _underlyingDict.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Dictionary<TKey, TValue> from) => Variant.CreateFrom(from); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Dictionary<TKey, TValue>(Variant from) => from.AsGodotDictionary<TKey, TValue>(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs index 6475237002..e6cb4171a7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs @@ -1,20 +1,16 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { public static class Dispatcher { - /// <summary> - /// Implements an external instance of GodotTaskScheduler. - /// </summary> - /// <returns>A GodotTaskScheduler instance.</returns> - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern GodotTaskScheduler godot_icall_DefaultGodotTaskScheduler(); + internal static GodotTaskScheduler DefaultGodotTaskScheduler; - /// <summary> - /// Initializes the synchronization context as the context of the GodotTaskScheduler. - /// </summary> - public static GodotSynchronizationContext SynchronizationContext => - godot_icall_DefaultGodotTaskScheduler().Context; + internal static void InitializeDefaultGodotTaskScheduler() + => DefaultGodotTaskScheduler = new GodotTaskScheduler(); + + public static GodotSynchronizationContext SynchronizationContext => DefaultGodotTaskScheduler.Context; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs new file mode 100644 index 0000000000..75793ea446 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +#nullable enable + +namespace Godot +{ + internal static class DisposablesTracker + { + [UnmanagedCallersOnly] + internal static void OnGodotShuttingDown() + { + try + { + OnGodotShuttingDownImpl(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + private static void OnGodotShuttingDownImpl() + { + bool isStdoutVerbose; + + try + { + isStdoutVerbose = OS.IsStdoutVerbose(); + } + catch (ObjectDisposedException) + { + // OS singleton already disposed. Maybe OnUnloading was called twice. + isStdoutVerbose = false; + } + + if (isStdoutVerbose) + GD.Print("Unloading: Disposing tracked instances..."); + + // Dispose Godot Objects first, and only then dispose other disposables + // like StringName, NodePath, Godot.Collections.Array/Dictionary, etc. + // The Godot Object Dispose() method may need any of the later instances. + + foreach (WeakReference<Object> item in GodotObjectInstances.Keys) + { + if (item.TryGetTarget(out Object? self)) + self.Dispose(); + } + + foreach (WeakReference<IDisposable> item in OtherInstances.Keys) + { + if (item.TryGetTarget(out IDisposable? self)) + self.Dispose(); + } + + if (isStdoutVerbose) + GD.Print("Unloading: Finished disposing tracked instances."); + } + + // ReSharper disable once RedundantNameQualifier + private static ConcurrentDictionary<WeakReference<Godot.Object>, byte> GodotObjectInstances { get; } = + new(); + + private static ConcurrentDictionary<WeakReference<IDisposable>, byte> OtherInstances { get; } = + new(); + + public static WeakReference<Object> RegisterGodotObject(Object godotObject) + { + var weakReferenceToSelf = new WeakReference<Object>(godotObject); + GodotObjectInstances.TryAdd(weakReferenceToSelf, 0); + return weakReferenceToSelf; + } + + public static WeakReference<IDisposable> RegisterDisposable(IDisposable disposable) + { + var weakReferenceToSelf = new WeakReference<IDisposable>(disposable); + OtherInstances.TryAdd(weakReferenceToSelf, 0); + return weakReferenceToSelf; + } + + public static void UnregisterGodotObject(Object godotObject, WeakReference<Object> weakReferenceToSelf) + { + if (!GodotObjectInstances.TryRemove(weakReferenceToSelf, out _)) + throw new ArgumentException("Godot Object not registered", nameof(weakReferenceToSelf)); + } + + public static void UnregisterDisposable(WeakReference<IDisposable> weakReference) + { + if (!OtherInstances.TryRemove(weakReference, out _)) + throw new ArgumentException("Disposable not registered", nameof(weakReference)); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs deleted file mode 100644 index 26d5f9c796..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; - -namespace Godot -{ - /// <summary> - /// Represents an <see cref="Object"/> whose members can be dynamically accessed at runtime through the Variant API. - /// </summary> - /// <remarks> - /// <para> - /// The <see cref="DynamicGodotObject"/> class enables access to the Variant - /// members of a <see cref="Object"/> instance at runtime. - /// </para> - /// <para> - /// This allows accessing the class members using their original names in the engine as well as the members from the - /// script attached to the <see cref="Object"/>, regardless of the scripting language it was written in. - /// </para> - /// </remarks> - /// <example> - /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Object"/>. - /// <code> - /// dynamic sprite = GetNode("Sprite2D").DynamicGodotObject; - /// sprite.add_child(this); - /// - /// if ((sprite.hframes * sprite.vframes) > 0) - /// sprite.frame = 0; - /// </code> - /// </example> - /// <example> - /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Object"/>. - /// <code> - /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject; - /// - /// if (childNode.print_allowed) - /// { - /// childNode.message = "Hello from C#"; - /// childNode.print_message(3); - /// } - /// </code> - /// The <c>ChildNode</c> node has the following GDScript script attached: - /// <code> - /// // # ChildNode.gd - /// // var print_allowed = true - /// // var message = "" - /// // - /// // func print_message(times): - /// // for i in times: - /// // print(message) - /// </code> - /// </example> - public class DynamicGodotObject : DynamicObject - { - /// <summary> - /// Gets the <see cref="Object"/> associated with this <see cref="DynamicGodotObject"/>. - /// </summary> - public Object Value { get; } - - /// <summary> - /// Initializes a new instance of the <see cref="DynamicGodotObject"/> class. - /// </summary> - /// <param name="godotObject"> - /// The <see cref="Object"/> that will be associated with this <see cref="DynamicGodotObject"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// Thrown when the <paramref name="godotObject"/> parameter is <see langword="null"/>. - /// </exception> - public DynamicGodotObject(Object godotObject) - { - if (godotObject == null) - throw new ArgumentNullException(nameof(godotObject)); - - Value = godotObject; - } - - /// <inheritdoc/> - public override IEnumerable<string> GetDynamicMemberNames() - { - return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value)); - } - - /// <inheritdoc/> - public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) - { - switch (binder.Operation) - { - case ExpressionType.Equal: - case ExpressionType.NotEqual: - if (binder.ReturnType == typeof(bool) || binder.ReturnType.IsAssignableFrom(typeof(bool))) - { - if (arg == null) - { - bool boolResult = Object.IsInstanceValid(Value); - - if (binder.Operation == ExpressionType.Equal) - boolResult = !boolResult; - - result = boolResult; - return true; - } - - if (arg is Object other) - { - bool boolResult = (Value == other); - - if (binder.Operation == ExpressionType.NotEqual) - boolResult = !boolResult; - - result = boolResult; - return true; - } - } - - break; - default: - // We're not implementing operators <, <=, >, and >= (LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual). - // These are used on the actual pointers in variant_op.cpp. It's better to let the user do that explicitly. - break; - } - - return base.TryBinaryOperation(binder, arg, out result); - } - - /// <inheritdoc/> - public override bool TryConvert(ConvertBinder binder, out object result) - { - if (binder.Type == typeof(Object)) - { - result = Value; - return true; - } - - if (typeof(Object).IsAssignableFrom(binder.Type)) - { - // Throws InvalidCastException when the cast fails - result = Convert.ChangeType(Value, binder.Type); - return true; - } - - return base.TryConvert(binder, out result); - } - - /// <inheritdoc/> - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - if (indexes.Length == 1) - { - if (indexes[0] is string name) - { - return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), name, out result); - } - } - - return base.TryGetIndex(binder, indexes, out result); - } - - /// <inheritdoc/> - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result); - } - - /// <inheritdoc/> - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result); - } - - /// <inheritdoc/> - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) - { - if (indexes.Length == 1) - { - if (indexes[0] is string name) - { - return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), name, value); - } - } - - return base.TrySetIndex(binder, indexes, value); - } - - /// <inheritdoc/> - public override bool TrySetMember(SetMemberBinder binder, object value) - { - return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value); - - #region We don't override these methods - - // Looks like this is not usable from C# - //public override bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result); - - // Object members cannot be deleted - //public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes); - //public override bool TryDeleteMember(DeleteMemberBinder binder); - - // Invocation on the object itself, e.g.: obj(param) - //public override bool TryInvoke(InvokeBinder binder, object[] args, out object result); - - // No unnary operations to handle - //public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result); - - #endregion - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs index 1dc21b6303..df0e839866 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs @@ -93,8 +93,12 @@ namespace Godot /// Negative indices access the children from the last one. /// To access a child node via its name, use <see cref="GetNode"/>. /// </summary> - /// <seealso cref="GetChildOrNull{T}(int)"/> + /// <seealso cref="GetChildOrNull{T}(int, bool)"/> /// <param name="idx">Child index.</param> + /// <param name="includeInternal"> + /// If <see langword="false"/>, internal children are skipped (see <c>internal</c> + /// parameter in <see cref="AddChild(Node, bool, InternalMode)"/>). + /// </param> /// <exception cref="InvalidCastException"> /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. /// </exception> @@ -102,9 +106,9 @@ namespace Godot /// <returns> /// The child <see cref="Node"/> at the given index <paramref name="idx"/>. /// </returns> - public T GetChild<T>(int idx) where T : class + public T GetChild<T>(int idx, bool includeInternal = false) where T : class { - return (T)(object)GetChild(idx); + return (T)(object)GetChild(idx, includeInternal); } /// <summary> @@ -113,15 +117,20 @@ namespace Godot /// Negative indices access the children from the last one. /// To access a child node via its name, use <see cref="GetNode"/>. /// </summary> - /// <seealso cref="GetChild{T}(int)"/> + /// <seealso cref="GetChild{T}(int, bool)"/> /// <param name="idx">Child index.</param> + /// <param name="includeInternal"> + /// If <see langword="false"/>, internal children are skipped (see <c>internal</c> + /// parameter in <see cref="AddChild(Node, bool, InternalMode)"/>). + /// </param> /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> /// <returns> /// The child <see cref="Node"/> at the given index <paramref name="idx"/>, or <see langword="null"/> if not found. /// </returns> - public T GetChildOrNull<T>(int idx) where T : class + public T GetChildOrNull<T>(int idx, bool includeInternal = false) where T : class { - return GetChild(idx) as T; + int count = GetChildCount(includeInternal); + return idx >= -count && idx < count ? GetChild(idx, includeInternal) as T : null; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs index 691fd85964..4094ceeb22 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -32,10 +32,17 @@ namespace Godot /// </returns> public static WeakRef WeakRef(Object obj) { - return godot_icall_Object_weakref(GetPtr(obj)); - } + if (!IsInstanceValid(obj)) + return null; + + NativeFuncs.godotsharp_weakref(GetPtr(obj), out godot_ref weakRef); + using (weakRef) + { + if (weakRef.IsNull) + return null; - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern WeakRef godot_icall_Object_weakref(IntPtr obj); + return (WeakRef)InteropUtils.UnmanagedGetManaged(weakRef.Reference); + } + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs deleted file mode 100644 index df130a5c77..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Godot.Collections; - -namespace Godot -{ - public partial class SceneTree - { - /// <summary> - /// Returns a list of all nodes assigned to the given <paramref name="group"/>. - /// </summary> - /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> - public Array<T> GetNodesInGroup<T>(StringName group) where T : class - { - return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(GetPtr(this), StringName.GetPtr(group), typeof(T))); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType); - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 236d0666bc..9e7da7757a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -1,11 +1,6 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -26,9 +21,11 @@ namespace Godot /// <param name="bytes">Byte array that will be decoded to a <c>Variant</c>.</param> /// <param name="allowObjects">If objects should be decoded.</param> /// <returns>The decoded <c>Variant</c>.</returns> - public static object Bytes2Var(byte[] bytes, bool allowObjects = false) + public static Variant Bytes2Var(Span<byte> bytes, bool allowObjects = false) { - return godot_icall_GD_bytes2var(bytes, allowObjects); + using var varBytes = Marshaling.ConvertSystemArrayToNativePackedByteArray(bytes); + NativeFuncs.godotsharp_bytes2var(varBytes, allowObjects.ToGodotBool(), out godot_variant ret); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); } /// <summary> @@ -46,9 +43,10 @@ namespace Godot /// </code> /// </example> /// <returns>The <c>Variant</c> converted to the given <paramref name="type"/>.</returns> - public static object Convert(object what, Variant.Type type) + public static Variant Convert(Variant what, Variant.Type type) { - return godot_icall_GD_convert(what, type); + NativeFuncs.godotsharp_convert((godot_variant)what.NativeVar, (int)type, out godot_variant ret); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); } /// <summary> @@ -62,7 +60,7 @@ namespace Godot return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); } - private static object[] GetPrintParams(object[] parameters) + private static string[] GetPrintParams(object[] parameters) { if (parameters == null) { @@ -82,9 +80,9 @@ namespace Godot /// </example> /// <param name="var">Variable that will be hashed.</param> /// <returns>Hash of the variable passed.</returns> - public static int Hash(object var) + public static int Hash(Variant var) { - return godot_icall_GD_hash(var); + return NativeFuncs.godotsharp_hash((godot_variant)var.NativeVar); } /// <summary> @@ -110,7 +108,7 @@ namespace Godot /// <returns>The <see cref="Object"/> instance.</returns> public static Object InstanceFromId(ulong instanceId) { - return godot_icall_GD_instance_from_id(instanceId); + return InteropUtils.UnmanagedGetManaged(NativeFuncs.godotsharp_instance_from_id(instanceId)); } /// <summary> @@ -191,8 +189,6 @@ namespace Godot /// Pushes an error message to Godot's built-in debugger and to the OS terminal. /// /// Note: Errors printed this way will not pause project execution. - /// To print an error message and pause project execution in debug builds, - /// use [code]assert(false, "test error")[/code] instead. /// </summary> /// <example> /// <code> @@ -202,7 +198,8 @@ namespace Godot /// <param name="message">Error message.</param> public static void PushError(string message) { - godot_icall_GD_pusherror(message); + using var godotStr = Marshaling.ConvertStringToNative(message); + NativeFuncs.godotsharp_pusherror(godotStr); } /// <summary> @@ -214,7 +211,8 @@ namespace Godot /// <param name="message">Warning message.</param> public static void PushWarning(string message) { - godot_icall_GD_pushwarning(message); + using var godotStr = Marshaling.ConvertStringToNative(message); + NativeFuncs.godotsharp_pushwarning(godotStr); } /// <summary> @@ -235,7 +233,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void Print(params object[] what) { - godot_icall_GD_print(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_print(godotStr); } /// <summary> @@ -264,7 +264,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintRich(params object[] what) { - godot_icall_GD_print_rich(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_print_rich(godotStr); } /// <summary> @@ -286,7 +288,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintErr(params object[] what) { - godot_icall_GD_printerr(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_printerr(godotStr); } /// <summary> @@ -306,7 +310,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintRaw(params object[] what) { - godot_icall_GD_printraw(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_printraw(godotStr); } /// <summary> @@ -320,7 +326,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintS(params object[] what) { - godot_icall_GD_prints(GetPrintParams(what)); + string str = string.Join(' ', GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_prints(godotStr); } /// <summary> @@ -334,7 +342,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintT(params object[] what) { - godot_icall_GD_printt(GetPrintParams(what)); + string str = string.Join('\t', GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_printt(godotStr); } /// <summary> @@ -348,7 +358,7 @@ namespace Godot /// <returns>A random <see langword="float"/> number.</returns> public static float Randf() { - return godot_icall_GD_randf(); + return NativeFuncs.godotsharp_randf(); } /// <summary> @@ -358,7 +368,7 @@ namespace Godot /// <returns>A random normally-distributed <see langword="float"/> number.</returns> public static double Randfn(double mean, double deviation) { - return godot_icall_GD_randfn(mean, deviation); + return NativeFuncs.godotsharp_randfn(mean, deviation); } /// <summary> @@ -376,7 +386,7 @@ namespace Godot /// <returns>A random <see langword="uint"/> number.</returns> public static uint Randi() { - return godot_icall_GD_randi(); + return NativeFuncs.godotsharp_randi(); } /// <summary> @@ -389,7 +399,7 @@ namespace Godot /// </summary> public static void Randomize() { - godot_icall_GD_randomize(); + NativeFuncs.godotsharp_randomize(); } /// <summary> @@ -404,7 +414,7 @@ namespace Godot /// <returns>A random <see langword="double"/> number inside the given range.</returns> public static double RandRange(double from, double to) { - return godot_icall_GD_randf_range(from, to); + return NativeFuncs.godotsharp_randf_range(from, to); } /// <summary> @@ -421,7 +431,7 @@ namespace Godot /// <returns>A random <see langword="int"/> number inside the given range.</returns> public static int RandRange(int from, int to) { - return godot_icall_GD_randi_range(from, to); + return NativeFuncs.godotsharp_randi_range(from, to); } /// <summary> @@ -434,7 +444,7 @@ namespace Godot /// <returns>A random <see langword="uint"/> number.</returns> public static uint RandFromSeed(ref ulong seed) { - return godot_icall_GD_rand_seed(seed, out seed); + return NativeFuncs.godotsharp_rand_from_seed(seed, out seed); } /// <summary> @@ -491,7 +501,7 @@ namespace Godot /// <param name="seed">Seed that will be used.</param> public static void Seed(ulong seed) { - godot_icall_GD_seed(seed); + NativeFuncs.godotsharp_seed(seed); } /// <summary> @@ -499,13 +509,16 @@ namespace Godot /// </summary> /// <param name="what">Arguments that will converted to string.</param> /// <returns>The string formed by the given arguments.</returns> - public static string Str(params object[] what) + public static string Str(params Variant[] what) { - return godot_icall_GD_str(what); + using var whatGodot = new Godot.Collections.Array(what); + NativeFuncs.godotsharp_str((godot_array)whatGodot.NativeValue, out godot_string ret); + using (ret) + return Marshaling.ConvertStringToManaged(ret); } /// <summary> - /// Converts a formatted string that was returned by <see cref="Var2Str(object)"/> to the original value. + /// Converts a formatted string that was returned by <see cref="Var2Str(Variant)"/> to the original value. /// </summary> /// <example> /// <code> @@ -516,32 +529,27 @@ namespace Godot /// </example> /// <param name="str">String that will be converted to Variant.</param> /// <returns>The decoded <c>Variant</c>.</returns> - public static object Str2Var(string str) + public static Variant Str2Var(string str) { - return godot_icall_GD_str2var(str); - } - - /// <summary> - /// Returns whether the given class exists in <see cref="ClassDB"/>. - /// </summary> - /// <returns>If the class exists in <see cref="ClassDB"/>.</returns> - public static bool TypeExists(StringName type) - { - return godot_icall_GD_type_exists(StringName.GetPtr(type)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_str2var(godotStr, out godot_variant ret); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); } /// <summary> /// Encodes a <c>Variant</c> value to a byte array. /// If <paramref name="fullObjects"/> is <see langword="true"/> encoding objects is allowed /// (and can potentially include code). - /// Deserialization can be done with <see cref="Bytes2Var(byte[], bool)"/>. + /// Deserialization can be done with <see cref="Bytes2Var(Span{byte}, bool)"/>. /// </summary> /// <param name="var">Variant that will be encoded.</param> /// <param name="fullObjects">If objects should be serialized.</param> /// <returns>The <c>Variant</c> encoded as an array of bytes.</returns> - public static byte[] Var2Bytes(object var, bool fullObjects = false) + public static byte[] Var2Bytes(Variant var, bool fullObjects = false) { - return godot_icall_GD_var2bytes(var, fullObjects); + NativeFuncs.godotsharp_var2bytes((godot_variant)var.NativeVar, fullObjects.ToGodotBool(), out var varBytes); + using (varBytes) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(varBytes); } /// <summary> @@ -561,9 +569,11 @@ namespace Godot /// </example> /// <param name="var">Variant that will be converted to string.</param> /// <returns>The <c>Variant</c> encoded as a string.</returns> - public static string Var2Str(object var) + public static string Var2Str(Variant var) { - return godot_icall_GD_var2str(var); + NativeFuncs.godotsharp_var2str((godot_variant)var.NativeVar, out godot_string ret); + using (ret) + return Marshaling.ConvertStringToManaged(ret); } /// <summary> @@ -572,85 +582,7 @@ namespace Godot /// <returns>The <see cref="Variant.Type"/> for the given <paramref name="type"/>.</returns> public static Variant.Type TypeToVariantType(Type type) { - return godot_icall_TypeToVariantType(type); + return Marshaling.ConvertManagedTypeToVariantType(type, out bool _); } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_GD_convert(object what, Variant.Type type); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_GD_hash(object var); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Object godot_icall_GD_instance_from_id(ulong instanceId); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_print(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_print_rich(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_printerr(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_printraw(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_prints(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_printt(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_randomize(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern uint godot_icall_GD_randi(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern float godot_icall_GD_randf(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_GD_randi_range(int from, int to); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern double godot_icall_GD_randf_range(double from, double to); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern double godot_icall_GD_randfn(double mean, double deviation); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_seed(ulong seed); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_GD_str(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_GD_str2var(string str); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_GD_type_exists(IntPtr type); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern byte[] godot_icall_GD_var2bytes(object what, bool fullObjects); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_GD_var2str(object var); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_pusherror(string type); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern 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/GodotTraceListener.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs index 9ccac1faaf..78a9d0fe9d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs @@ -17,10 +17,7 @@ namespace Godot public override void Fail(string message, string detailMessage) { GD.PrintErr("Assertion failed: ", message); - if (detailMessage != null) - { - GD.PrintErr(" Details: ", detailMessage); - } + GD.PrintErr(" Details: ", detailMessage); try { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs index 729529d093..a17741ddeb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs @@ -1,17 +1,33 @@ using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { public static partial class GD { - /// <summary> - /// Fires when an unhandled exception occurs, regardless of project settings. - /// </summary> - public static event EventHandler<UnhandledExceptionArgs> UnhandledException; - - private static void OnUnhandledException(Exception e) + [UnmanagedCallersOnly] + internal static void OnCoreApiAssemblyLoaded(godot_bool isDebug) { - UnhandledException?.Invoke(null, new UnhandledExceptionArgs(e)); + try + { + Dispatcher.InitializeDefaultGodotTaskScheduler(); + + if (isDebug.ToBool()) + { + DebuggingUtils.InstallTraceListener(); + + AppDomain.CurrentDomain.UnhandledException += (_, e) => + { + // Exception.ToString() includes the inner exception + ExceptionUtils.LogUnhandledException((Exception)e.ExceptionObject); + }; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs deleted file mode 100644 index 50ae2eb112..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Godot -{ - internal static class MarshalUtils - { - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Collections.Array{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericArray(Type type) => - type.GetGenericTypeDefinition() == typeof(Collections.Array<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(Collections.Dictionary<,>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="List{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsSystemGenericList(Type type) => - type.GetGenericTypeDefinition() == typeof(List<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsSystemGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(Dictionary<,>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="IEnumerable{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericIEnumerable(Type type) => type.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="ICollection{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericICollection(Type type) => type.GetGenericTypeDefinition() == typeof(ICollection<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="IDictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericIDictionary(Type type) => type.GetGenericTypeDefinition() == typeof(IDictionary<,>); - - /// <summary> - /// Returns <see langword="true"/> if the <see cref="FlagsAttribute"/> is applied to the given type. - /// </summary> - private static bool TypeHasFlagsAttribute(Type type) => type.IsDefined(typeof(FlagsAttribute), false); - - /// <summary> - /// Returns the generic type definition of <paramref name="type"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static void GetGenericTypeDefinition(Type type, out Type genericTypeDefinition) - { - genericTypeDefinition = type.GetGenericTypeDefinition(); - } - - /// <summary> - /// Gets the element type for the given <paramref name="arrayType"/>. - /// </summary> - /// <param name="arrayType">Type for the generic array.</param> - /// <param name="elementType">Element type for the generic array.</param> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="arrayType"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static void ArrayGetElementType(Type arrayType, out Type elementType) - { - elementType = arrayType.GetGenericArguments()[0]; - } - - /// <summary> - /// Gets the key type and the value type for the given <paramref name="dictionaryType"/>. - /// </summary> - /// <param name="dictionaryType">The type for the generic dictionary.</param> - /// <param name="keyType">Key type for the generic dictionary.</param> - /// <param name="valueType">Value type for the generic dictionary.</param> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="dictionaryType"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) - { - var genericArgs = dictionaryType.GetGenericArguments(); - keyType = genericArgs[0]; - valueType = genericArgs[1]; - } - - /// <summary> - /// Constructs a new <see cref="Type"/> from <see cref="Collections.Array{T}"/> - /// where the generic type for the elements is <paramref name="elemType"/>. - /// </summary> - /// <param name="elemType">Element type for the array.</param> - /// <returns>The generic array type with the specified element type.</returns> - private static Type MakeGenericArrayType(Type elemType) - { - return typeof(Collections.Array<>).MakeGenericType(elemType); - } - - /// <summary> - /// Constructs a new <see cref="Type"/> from <see cref="Collections.Dictionary{TKey, TValue}"/> - /// where the generic type for the keys is <paramref name="keyType"/> and - /// for the values is <paramref name="valueType"/>. - /// </summary> - /// <param name="keyType">Key type for the dictionary.</param> - /// <param name="valueType">Key type for the dictionary.</param> - /// <returns>The generic dictionary type with the specified key and value types.</returns> - private static Type MakeGenericDictionaryType(Type keyType, Type valueType) - { - return typeof(Collections.Dictionary<,>).MakeGenericType(keyType, valueType); - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index 36b7d0f80f..41a0dd871c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; namespace Godot @@ -759,9 +754,10 @@ namespace Godot } /// <summary> - /// Returns the [code]value[/code] wrapped between [code]0[/code] and the [code]length[/code]. - /// If the limit is reached, the next value the function returned is decreased to the [code]0[/code] side or increased to the [code]length[/code] side (like a triangle wave). - /// If [code]length[/code] is less than zero, it becomes positive. + /// Returns the <paramref name="value"/> wrapped between <c>0</c> and the <paramref name="length"/>. + /// If the limit is reached, the next value the function returned is decreased to the <c>0</c> side + /// or increased to the <paramref name="length"/> side (like a triangle wave). + /// If <paramref name="length"/> is less than zero, it becomes positive. /// </summary> /// <param name="value">The value to pingpong.</param> /// <param name="length">The maximum value of the function.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs index f15d01b34b..ea05c1547c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; namespace Godot diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs new file mode 100644 index 0000000000..afef20a7f2 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs @@ -0,0 +1,313 @@ +using System.Runtime.CompilerServices; + +namespace Godot.NativeInterop; + +// Ref structs are not allowed as generic type parameters, so we can't use Unsafe.AsPointer<T>/AsRef<T>. +// As a workaround we create our own overloads for our structs with some tricks under the hood. + +public static class CustomUnsafe +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_ref* AsPointer(ref godot_ref value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_ref* ReadOnlyRefAsPointer(in godot_ref value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_ref AsRef(godot_ref* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_ref AsRef(in godot_ref source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant_call_error* AsPointer(ref godot_variant_call_error value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant_call_error* ReadOnlyRefAsPointer(in godot_variant_call_error value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant_call_error AsRef(godot_variant_call_error* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant_call_error AsRef(in godot_variant_call_error source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant* AsPointer(ref godot_variant value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant* ReadOnlyRefAsPointer(in godot_variant value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant AsRef(godot_variant* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant AsRef(in godot_variant source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string* AsPointer(ref godot_string value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string* ReadOnlyRefAsPointer(in godot_string value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string AsRef(godot_string* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string AsRef(in godot_string source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string_name* AsPointer(ref godot_string_name value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string_name* ReadOnlyRefAsPointer(in godot_string_name value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string_name AsRef(godot_string_name* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string_name AsRef(in godot_string_name source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_node_path* AsPointer(ref godot_node_path value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_node_path* ReadOnlyRefAsPointer(in godot_node_path value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_node_path AsRef(godot_node_path* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_node_path AsRef(in godot_node_path source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_signal* AsPointer(ref godot_signal value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_signal* ReadOnlyRefAsPointer(in godot_signal value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_signal AsRef(godot_signal* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_signal AsRef(in godot_signal source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_callable* AsPointer(ref godot_callable value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_callable* ReadOnlyRefAsPointer(in godot_callable value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_callable AsRef(godot_callable* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_callable AsRef(in godot_callable source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_array* AsPointer(ref godot_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_array* ReadOnlyRefAsPointer(in godot_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_array AsRef(godot_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_array AsRef(in godot_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_dictionary* AsPointer(ref godot_dictionary value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_dictionary* ReadOnlyRefAsPointer(in godot_dictionary value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_dictionary AsRef(godot_dictionary* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_dictionary AsRef(in godot_dictionary source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_byte_array* AsPointer(ref godot_packed_byte_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_byte_array* ReadOnlyRefAsPointer(in godot_packed_byte_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_byte_array AsRef(godot_packed_byte_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_byte_array AsRef(in godot_packed_byte_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int32_array* AsPointer(ref godot_packed_int32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int32_array* ReadOnlyRefAsPointer(in godot_packed_int32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int32_array AsRef(godot_packed_int32_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int32_array AsRef(in godot_packed_int32_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int64_array* AsPointer(ref godot_packed_int64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int64_array* ReadOnlyRefAsPointer(in godot_packed_int64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int64_array AsRef(godot_packed_int64_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int64_array AsRef(in godot_packed_int64_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float32_array* AsPointer(ref godot_packed_float32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float32_array* ReadOnlyRefAsPointer(in godot_packed_float32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float32_array AsRef(godot_packed_float32_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float32_array AsRef(in godot_packed_float32_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float64_array* AsPointer(ref godot_packed_float64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float64_array* ReadOnlyRefAsPointer(in godot_packed_float64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float64_array AsRef(godot_packed_float64_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float64_array AsRef(in godot_packed_float64_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_string_array* AsPointer(ref godot_packed_string_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_string_array* ReadOnlyRefAsPointer(in godot_packed_string_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_string_array AsRef(godot_packed_string_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_string_array AsRef(in godot_packed_string_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector2_array* AsPointer(ref godot_packed_vector2_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector2_array* ReadOnlyRefAsPointer(in godot_packed_vector2_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector2_array AsRef(godot_packed_vector2_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector2_array AsRef(in godot_packed_vector2_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector3_array* AsPointer(ref godot_packed_vector3_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector3_array* ReadOnlyRefAsPointer(in godot_packed_vector3_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector3_array AsRef(godot_packed_vector3_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector3_array AsRef(in godot_packed_vector3_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_color_array* AsPointer(ref godot_packed_color_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_color_array* ReadOnlyRefAsPointer(in godot_packed_color_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_color_array AsRef(godot_packed_color_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_color_array AsRef(in godot_packed_color_array source) + => ref *ReadOnlyRefAsPointer(in source); +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs new file mode 100644 index 0000000000..5a0ea2ba13 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +#nullable enable + +namespace Godot.NativeInterop +{ + internal static class ExceptionUtils + { + public static void PushError(string message) + { + GD.PushError(message); + } + + private static void OnExceptionLoggerException(Exception loggerException, Exception exceptionToLog) + { + try + { + // This better not throw + PushError(string.Concat("Exception thrown while trying to log another exception...", + "\n### Exception ###\n", exceptionToLog.ToString(), + "\n### Logger exception ###\n", loggerException.ToString())); + } + catch (Exception) + { + // Well, too bad... + } + } + + private record struct StackInfoTuple(string? File, string Func, int Line); + + private static void CollectExceptionInfo(Exception exception, List<StackInfoTuple> globalFrames, + StringBuilder excMsg) + { + if (excMsg.Length > 0) + excMsg.Append(" ---> "); + excMsg.Append(exception.GetType().FullName); + excMsg.Append(": "); + excMsg.Append(exception.Message); + + var innerExc = exception.InnerException; + + if (innerExc != null) + { + CollectExceptionInfo(innerExc, globalFrames, excMsg); + globalFrames.Add(new("", "--- End of inner exception stack trace ---", 0)); + } + + var stackTrace = new StackTrace(exception, fNeedFileInfo: true); + + foreach (StackFrame frame in stackTrace.GetFrames()) + { + DebuggingUtils.GetStackFrameMethodDecl(frame, out string methodDecl); + globalFrames.Add(new(frame.GetFileName(), methodDecl, frame.GetFileLineNumber())); + } + } + + private static void SendToScriptDebugger(Exception e) + { + var globalFrames = new List<StackInfoTuple>(); + + var excMsg = new StringBuilder(); + + CollectExceptionInfo(e, globalFrames, excMsg); + + string file = globalFrames.Count > 0 ? globalFrames[0].File ?? "" : ""; + string func = globalFrames.Count > 0 ? globalFrames[0].Func : ""; + int line = globalFrames.Count > 0 ? globalFrames[0].Line : 0; + string errorMsg = "Exception"; + + using godot_string nFile = Marshaling.ConvertStringToNative(file); + using godot_string nFunc = Marshaling.ConvertStringToNative(func); + using godot_string nErrorMsg = Marshaling.ConvertStringToNative(errorMsg); + using godot_string nExcMsg = Marshaling.ConvertStringToNative(excMsg.ToString()); + + using DebuggingUtils.godot_stack_info_vector stackInfoVector = default; + + stackInfoVector.Resize(globalFrames.Count); + + unsafe + { + for (int i = 0; i < globalFrames.Count; i++) + { + DebuggingUtils.godot_stack_info* stackInfo = &stackInfoVector.Elements[i]; + + var globalFrame = globalFrames[i]; + + // Assign directly to element in Vector. This way we don't need to worry + // about disposal if an exception is thrown. The Vector takes care of it. + stackInfo->File = Marshaling.ConvertStringToNative(globalFrame.File); + stackInfo->Func = Marshaling.ConvertStringToNative(globalFrame.Func); + stackInfo->Line = globalFrame.Line; + } + + NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line, + nErrorMsg, nExcMsg, p_warning: godot_bool.False, stackInfoVector); + } + } + + public static void LogException(Exception e) + { + try + { + if (NativeFuncs.godotsharp_internal_script_debugger_is_active()) + { + SendToScriptDebugger(e); + } + else + { + GD.PushError(e.ToString()); + } + } + catch (Exception unexpected) + { + OnExceptionLoggerException(unexpected, e); + } + } + + public static void LogUnhandledException(Exception e) + { + try + { + if (NativeFuncs.godotsharp_internal_script_debugger_is_active()) + { + SendToScriptDebugger(e); + } + + // In this case, print it as well in addition to sending it to the script debugger + GD.PushError("Unhandled exception\n" + e); + } + catch (Exception unexpected) + { + OnExceptionLoggerException(unexpected, e); + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs new file mode 100644 index 0000000000..5579992d2b --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs @@ -0,0 +1,59 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Godot.NativeInterop +{ + public class GodotDllImportResolver + { + private IntPtr _internalHandle; + + public GodotDllImportResolver(IntPtr internalHandle) + { + _internalHandle = internalHandle; + } + + public IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName == "__Internal") + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Win32.GetModuleHandle(IntPtr.Zero); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return _internalHandle; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MacOS.dlopen(IntPtr.Zero, MacOS.RTLD_LAZY); + } + } + + return IntPtr.Zero; + } + + // ReSharper disable InconsistentNaming + private static class MacOS + { + private const string SystemLibrary = "/usr/lib/libSystem.dylib"; + + public const int RTLD_LAZY = 1; + + [DllImport(SystemLibrary)] + public static extern IntPtr dlopen(IntPtr path, int mode); + } + + private static class Win32 + { + private const string SystemLibrary = "Kernel32.dll"; + + [DllImport(SystemLibrary)] + public static extern IntPtr GetModuleHandle(IntPtr lpModuleName); + } + // ReSharper restore InconsistentNaming + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs new file mode 100644 index 0000000000..44806e8ecf --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -0,0 +1,1091 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Godot.NativeInterop +{ + // NOTES: + // ref structs cannot implement interfaces, but they still work in `using` directives if they declare Dispose() + + public static class GodotBoolExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_bool ToGodotBool(this bool @bool) + { + return *(godot_bool*)&@bool; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool ToBool(this godot_bool godotBool) + { + return *(bool*)&godotBool; + } + } + + // Apparently a struct with a byte is not blittable? It crashes when calling a UnmanagedCallersOnly function ptr. + // ReSharper disable once InconsistentNaming + public enum godot_bool : byte + { + True = 1, + False = 0 + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_ref + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_ref* GetUnsafeAddress() + => (godot_ref*)Unsafe.AsPointer(ref Unsafe.AsRef(in _reference)); + + private IntPtr _reference; + + public void Dispose() + { + if (_reference == IntPtr.Zero) + return; + NativeFuncs.godotsharp_ref_destroy(ref this); + _reference = IntPtr.Zero; + } + + public readonly IntPtr Reference + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _reference; + } + + public readonly bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _reference == IntPtr.Zero; + } + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum godot_variant_call_error_error + { + GODOT_CALL_ERROR_CALL_OK = 0, + GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD, + GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT, + GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS, + GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS, + GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL, + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_variant_call_error + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_variant_call_error* GetUnsafeAddress() + => (godot_variant_call_error*)Unsafe.AsPointer(ref Unsafe.AsRef(in error)); + + private godot_variant_call_error_error error; + private int argument; + private int expected; + + public godot_variant_call_error_error Error + { + readonly get => error; + set => error = value; + } + + public int Argument + { + readonly get => argument; + set => argument = value; + } + + public Godot.Variant.Type Expected + { + readonly get => (Godot.Variant.Type)expected; + set => expected = (int)value; + } + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_variant + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_variant* GetUnsafeAddress() + => (godot_variant*)Unsafe.AsPointer(ref Unsafe.AsRef(in _typeField)); + + // Variant.Type is generated as an enum of type long, so we can't use for the field as it must only take 32-bits. + [FieldOffset(0)] private int _typeField; + + // There's padding here + + [FieldOffset(8)] private godot_variant_data _data; + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + private unsafe ref struct godot_variant_data + { + [FieldOffset(0)] public godot_bool _bool; + [FieldOffset(0)] public long _int; + [FieldOffset(0)] public double _float; + [FieldOffset(0)] public Transform2D* _transform2D; + [FieldOffset(0)] public AABB* _aabb; + [FieldOffset(0)] public Basis* _basis; + [FieldOffset(0)] public Transform3D* _transform3D; + [FieldOffset(0)] public Vector4* _vector4; + [FieldOffset(0)] public Vector4i* _vector4i; + [FieldOffset(0)] public Projection* _projection; + [FieldOffset(0)] private godot_variant_data_mem _mem; + + // The following fields are not in the C++ union, but this is how they're stored in _mem. + [FieldOffset(0)] public godot_string_name _m_string_name; + [FieldOffset(0)] public godot_string _m_string; + [FieldOffset(0)] public Vector3 _m_vector3; + [FieldOffset(0)] public Vector3i _m_vector3i; + [FieldOffset(0)] public Vector2 _m_vector2; + [FieldOffset(0)] public Vector2i _m_vector2i; + [FieldOffset(0)] public Rect2 _m_rect2; + [FieldOffset(0)] public Rect2i _m_rect2i; + [FieldOffset(0)] public Plane _m_plane; + [FieldOffset(0)] public Quaternion _m_quaternion; + [FieldOffset(0)] public Color _m_color; + [FieldOffset(0)] public godot_node_path _m_node_path; + [FieldOffset(0)] public RID _m_rid; + [FieldOffset(0)] public godot_variant_obj_data _m_obj_data; + [FieldOffset(0)] public godot_callable _m_callable; + [FieldOffset(0)] public godot_signal _m_signal; + [FieldOffset(0)] public godot_dictionary _m_dictionary; + [FieldOffset(0)] public godot_array _m_array; + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public struct godot_variant_obj_data + { + public ulong id; + public IntPtr obj; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public struct godot_variant_data_mem + { +#pragma warning disable 169 + private real_t _mem0; + private real_t _mem1; + private real_t _mem2; + private real_t _mem3; +#pragma warning restore 169 + } + } + + public Variant.Type Type + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => (Variant.Type)_typeField; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _typeField = (int)value; + } + + public godot_bool Bool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._bool; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._bool = value; + } + + public long Int + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._int; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._int = value; + } + + public double Float + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._float; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._float = value; + } + + public readonly unsafe Transform2D* Transform2D + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._transform2D; + } + + public readonly unsafe AABB* AABB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._aabb; + } + + public readonly unsafe Basis* Basis + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._basis; + } + + public readonly unsafe Transform3D* Transform3D + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._transform3D; + } + + public readonly unsafe Vector4* Vector4 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._vector4; + } + + public readonly unsafe Vector4i* Vector4i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._vector4i; + } + + public readonly unsafe Projection* Projection + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._projection; + } + + public godot_string_name StringName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_string_name; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_string_name = value; + } + + public godot_string String + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_string; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_string = value; + } + + public Vector3 Vector3 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector3; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector3 = value; + } + + public Vector3i Vector3i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector3i; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector3i = value; + } + + public Vector2 Vector2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector2 = value; + } + + public Vector2i Vector2i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector2i; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector2i = value; + } + + public Rect2 Rect2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_rect2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_rect2 = value; + } + + public Rect2i Rect2i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_rect2i; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_rect2i = value; + } + + public Plane Plane + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_plane; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_plane = value; + } + + public Quaternion Quaternion + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_quaternion; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_quaternion = value; + } + + public Color Color + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_color; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_color = value; + } + + public godot_node_path NodePath + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_node_path; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_node_path = value; + } + + public RID RID + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_rid; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_rid = value; + } + + public godot_callable Callable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_callable; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_callable = value; + } + + public godot_signal Signal + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_signal; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_signal = value; + } + + public godot_dictionary Dictionary + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_dictionary; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_dictionary = value; + } + + public godot_array Array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_array; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_array = value; + } + + public readonly IntPtr Object + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._m_obj_data.obj; + } + + public void Dispose() + { + switch (Type) + { + case Variant.Type.Nil: + case Variant.Type.Bool: + case Variant.Type.Int: + case Variant.Type.Float: + case Variant.Type.Vector2: + case Variant.Type.Vector2i: + case Variant.Type.Rect2: + case Variant.Type.Rect2i: + case Variant.Type.Vector3: + case Variant.Type.Vector3i: + case Variant.Type.Plane: + case Variant.Type.Quaternion: + case Variant.Type.Color: + case Variant.Type.Rid: + return; + } + + NativeFuncs.godotsharp_variant_destroy(ref this); + Type = Variant.Type.Nil; + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + // Variant.Type is generated as an enum of type long, so we can't use for the field as it must only take 32-bits. + [FieldOffset(0)] private int _typeField; + + // There's padding here + + [FieldOffset(8)] private godot_variant_data.godot_variant_data_mem _data; + + public static unsafe explicit operator movable(in godot_variant value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_variant(movable value) + => *(godot_variant*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_variant DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_variant*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_string + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_string* GetUnsafeAddress() + => (godot_string*)Unsafe.AsPointer(ref Unsafe.AsRef(in _ptr)); + + private IntPtr _ptr; + + public void Dispose() + { + if (_ptr == IntPtr.Zero) + return; + NativeFuncs.godotsharp_string_destroy(ref this); + _ptr = IntPtr.Zero; + } + + public readonly IntPtr Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + // Size including the null termination character + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != IntPtr.Zero ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_string_name + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_string_name* GetUnsafeAddress() + => (godot_string_name*)Unsafe.AsPointer(ref Unsafe.AsRef(in _data)); + + private IntPtr _data; + + public void Dispose() + { + if (_data == IntPtr.Zero) + return; + NativeFuncs.godotsharp_string_name_destroy(ref this); + _data = IntPtr.Zero; + } + + public readonly bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data != IntPtr.Zero; + } + + public readonly bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // This is all that's needed to check if it's empty. Equivalent to `== StringName()` in C++. + get => _data == IntPtr.Zero; + } + + public static bool operator ==(godot_string_name left, godot_string_name right) + { + return left._data == right._data; + } + + public static bool operator !=(godot_string_name left, godot_string_name right) + { + return !(left == right); + } + + public bool Equals(godot_string_name other) + { + return _data == other._data; + } + + public override bool Equals(object obj) + { + return obj is StringName s && s.Equals(this); + } + + public override int GetHashCode() + { + return _data.GetHashCode(); + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private IntPtr _data; + + public static unsafe explicit operator movable(in godot_string_name value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_string_name(movable value) + => *(godot_string_name*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_string_name DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_string_name*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_node_path + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_node_path* GetUnsafeAddress() + => (godot_node_path*)Unsafe.AsPointer(ref Unsafe.AsRef(in _data)); + + private IntPtr _data; + + public void Dispose() + { + if (_data == IntPtr.Zero) + return; + NativeFuncs.godotsharp_node_path_destroy(ref this); + _data = IntPtr.Zero; + } + + public readonly bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data != IntPtr.Zero; + } + + public readonly bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // This is all that's needed to check if it's empty. It's what the `is_empty()` C++ method does. + get => _data == IntPtr.Zero; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private IntPtr _data; + + public static unsafe explicit operator movable(in godot_node_path value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_node_path(movable value) + => *(godot_node_path*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_node_path DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_node_path*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_signal + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_signal* GetUnsafeAddress() + => (godot_signal*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper)); + + [FieldOffset(0)] private byte _getUnsafeAddressHelper; + + [FieldOffset(0)] private godot_string_name _name; + + // There's padding here on 32-bit + + [FieldOffset(8)] private ulong _objectId; + + public godot_signal(godot_string_name name, ulong objectId) : this() + { + _name = name; + _objectId = objectId; + } + + public godot_string_name Name + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _name; + } + + public ulong ObjectId + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _objectId; + } + + public void Dispose() + { + if (!_name.IsAllocated) + return; + NativeFuncs.godotsharp_signal_destroy(ref this); + _name = default; + } + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_callable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_callable* GetUnsafeAddress() + => (godot_callable*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper)); + + [FieldOffset(0)] private byte _getUnsafeAddressHelper; + + [FieldOffset(0)] private godot_string_name _method; + + // There's padding here on 32-bit + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + [FieldOffset(8)] private ulong _objectId; + [FieldOffset(8)] private IntPtr _custom; + + public godot_callable(godot_string_name method, ulong objectId) : this() + { + _method = method; + _objectId = objectId; + } + + public void Dispose() + { + // _custom needs freeing as well + if (!_method.IsAllocated && _custom == IntPtr.Zero) + return; + NativeFuncs.godotsharp_callable_destroy(ref this); + _method = default; + _custom = IntPtr.Zero; + } + } + + // A correctly constructed value needs to call the native default constructor to allocate `_p`. + // Don't pass a C# default constructed `godot_array` to native code, unless it's going to + // be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine). + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_array* GetUnsafeAddress() + => (godot_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper)); + + [FieldOffset(0)] private byte _getUnsafeAddressHelper; + + [FieldOffset(0)] private unsafe ArrayPrivate* _p; + + [StructLayout(LayoutKind.Sequential)] + private struct ArrayPrivate + { + private uint _safeRefCount; + + public VariantVector _arrayVector; + // There are more fields here, but we don't care as we never store this in C# + + public readonly int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arrayVector.Size; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct VariantVector + { + private IntPtr _writeProxy; + public unsafe godot_variant* _ptr; + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + public readonly unsafe godot_variant* Elements + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p->_arrayVector._ptr; + } + + public readonly unsafe bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p != null; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p != null ? _p->Size : 0; + } + + public unsafe void Dispose() + { + if (_p == null) + return; + NativeFuncs.godotsharp_array_destroy(ref this); + _p = null; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private unsafe ArrayPrivate* _p; + + public static unsafe explicit operator movable(in godot_array value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_array(movable value) + => *(godot_array*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_array DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_array*)Unsafe.AsPointer(ref this)); + } + } + + // IMPORTANT: + // A correctly constructed value needs to call the native default constructor to allocate `_p`. + // Don't pass a C# default constructed `godot_dictionary` to native code, unless it's going to + // be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine). + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_dictionary + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_dictionary* GetUnsafeAddress() + => (godot_dictionary*)Unsafe.AsPointer(ref Unsafe.AsRef(in _p)); + + private IntPtr _p; + + public readonly bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p != IntPtr.Zero; + } + + public void Dispose() + { + if (_p == IntPtr.Zero) + return; + NativeFuncs.godotsharp_dictionary_destroy(ref this); + _p = IntPtr.Zero; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private IntPtr _p; + + public static unsafe explicit operator movable(in godot_dictionary value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_dictionary(movable value) + => *(godot_dictionary*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_dictionary DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_dictionary*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_byte_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_byte_array* GetUnsafeAddress() + => (godot_packed_byte_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe byte* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_byte_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe byte* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_int32_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_int32_array* GetUnsafeAddress() + => (godot_packed_int32_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe int* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_int32_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe int* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *(_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_int64_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_int64_array* GetUnsafeAddress() + => (godot_packed_int64_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe long* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_int64_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe long* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_float32_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_float32_array* GetUnsafeAddress() + => (godot_packed_float32_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe float* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_float32_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe float* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_float64_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_float64_array* GetUnsafeAddress() + => (godot_packed_float64_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe double* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_float64_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe double* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_string_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_string_array* GetUnsafeAddress() + => (godot_packed_string_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe godot_string* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_string_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe godot_string* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_vector2_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_vector2_array* GetUnsafeAddress() + => (godot_packed_vector2_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe Vector2* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_vector2_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe Vector2* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_vector3_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_vector3_array* GetUnsafeAddress() + => (godot_packed_vector3_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe Vector3* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_vector3_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe Vector3* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_color_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_color_array* GetUnsafeAddress() + => (godot_packed_color_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe Color* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_color_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe Color* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs new file mode 100644 index 0000000000..82f1c04d40 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs @@ -0,0 +1,96 @@ +using System; +using System.Runtime.InteropServices; +using Godot.Bridge; + +// ReSharper disable InconsistentNaming + +namespace Godot.NativeInterop +{ + internal static class InteropUtils + { + public static Object UnmanagedGetManaged(IntPtr unmanaged) + { + // The native pointer may be null + if (unmanaged == IntPtr.Zero) + return null; + + IntPtr gcHandlePtr; + godot_bool hasCsScriptInstance; + + // First try to get the tied managed instance from a CSharpInstance script instance + + gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_get_script_instance_managed( + unmanaged, out hasCsScriptInstance); + + if (gcHandlePtr != IntPtr.Zero) + return (Object)GCHandle.FromIntPtr(gcHandlePtr).Target; + + // Otherwise, if the object has a CSharpInstance script instance, return null + + if (hasCsScriptInstance.ToBool()) + return null; + + // If it doesn't have a CSharpInstance script instance, try with native instance bindings + + gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_get_instance_binding_managed(unmanaged); + + object target = gcHandlePtr != IntPtr.Zero ? GCHandle.FromIntPtr(gcHandlePtr).Target : null; + + if (target != null) + return (Object)target; + + // If the native instance binding GC handle target was collected, create a new one + + gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_instance_binding_create_managed( + unmanaged, gcHandlePtr); + + return gcHandlePtr != IntPtr.Zero ? (Object)GCHandle.FromIntPtr(gcHandlePtr).Target : null; + } + + public static void TieManagedToUnmanaged(Object managed, IntPtr unmanaged, + StringName nativeName, bool refCounted, Type type, Type nativeType) + { + var gcHandle = refCounted ? + CustomGCHandle.AllocWeak(managed) : + CustomGCHandle.AllocStrong(managed, type); + + if (type == nativeType) + { + var nativeNameSelf = (godot_string_name)nativeName.NativeValue; + NativeFuncs.godotsharp_internal_tie_native_managed_to_unmanaged( + GCHandle.ToIntPtr(gcHandle), unmanaged, nativeNameSelf, refCounted.ToGodotBool()); + } + else + { + unsafe + { + // We don't dispose `script` ourselves here. + // `tie_user_managed_to_unmanaged` does it for us to avoid another P/Invoke call. + godot_ref script; + ScriptManagerBridge.GetOrLoadOrCreateScriptForType(type, &script); + + // IMPORTANT: This must be called after GetOrCreateScriptBridgeForType + NativeFuncs.godotsharp_internal_tie_user_managed_to_unmanaged( + GCHandle.ToIntPtr(gcHandle), unmanaged, &script, refCounted.ToGodotBool()); + } + } + } + + public static void TieManagedToUnmanagedWithPreSetup(Object managed, IntPtr unmanaged, + Type type, Type nativeType) + { + if (type == nativeType) + return; + + var strongGCHandle = CustomGCHandle.AllocStrong(managed); + NativeFuncs.godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup( + GCHandle.ToIntPtr(strongGCHandle), unmanaged); + } + + public static Object EngineGetSingleton(string name) + { + using godot_string src = Marshaling.ConvertStringToNative(name); + return UnmanagedGetManaged(NativeFuncs.godotsharp_engine_get_singleton(src)); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs new file mode 100644 index 0000000000..eee19aea46 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -0,0 +1,1092 @@ +using System; +using System.Runtime.InteropServices; + +// ReSharper disable InconsistentNaming + +// We want to use full name qualifiers here even if redundant for clarity +// ReSharper disable RedundantNameQualifier + +#nullable enable + +namespace Godot.NativeInterop +{ + public static class Marshaling + { + internal static Variant.Type ConvertManagedTypeToVariantType(Type type, out bool r_nil_is_variant) + { + r_nil_is_variant = false; + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return Variant.Type.Bool; + case TypeCode.Char: + return Variant.Type.Int; + case TypeCode.SByte: + return Variant.Type.Int; + case TypeCode.Int16: + return Variant.Type.Int; + case TypeCode.Int32: + return Variant.Type.Int; + case TypeCode.Int64: + return Variant.Type.Int; + case TypeCode.Byte: + return Variant.Type.Int; + case TypeCode.UInt16: + return Variant.Type.Int; + case TypeCode.UInt32: + return Variant.Type.Int; + case TypeCode.UInt64: + return Variant.Type.Int; + case TypeCode.Single: + return Variant.Type.Float; + case TypeCode.Double: + return Variant.Type.Float; + case TypeCode.String: + return Variant.Type.String; + default: + { + if (type == typeof(Vector2)) + return Variant.Type.Vector2; + + if (type == typeof(Vector2i)) + return Variant.Type.Vector2i; + + if (type == typeof(Rect2)) + return Variant.Type.Rect2; + + if (type == typeof(Rect2i)) + return Variant.Type.Rect2i; + + if (type == typeof(Transform2D)) + return Variant.Type.Transform2d; + + if (type == typeof(Vector3)) + return Variant.Type.Vector3; + + if (type == typeof(Vector3i)) + return Variant.Type.Vector3i; + + if (type == typeof(Vector4)) + return Variant.Type.Vector4; + + if (type == typeof(Vector4i)) + return Variant.Type.Vector4i; + + if (type == typeof(Basis)) + return Variant.Type.Basis; + + if (type == typeof(Quaternion)) + return Variant.Type.Quaternion; + + if (type == typeof(Transform3D)) + return Variant.Type.Transform3d; + + if (type == typeof(Projection)) + return Variant.Type.Projection; + + if (type == typeof(AABB)) + return Variant.Type.Aabb; + + if (type == typeof(Color)) + return Variant.Type.Color; + + if (type == typeof(Plane)) + return Variant.Type.Plane; + + if (type == typeof(Callable)) + return Variant.Type.Callable; + + if (type == typeof(SignalInfo)) + return Variant.Type.Signal; + + if (type.IsEnum) + return Variant.Type.Int; + + if (type.IsArray || type.IsSZArray) + { + if (type == typeof(byte[])) + return Variant.Type.PackedByteArray; + + if (type == typeof(int[])) + return Variant.Type.PackedInt32Array; + + if (type == typeof(long[])) + return Variant.Type.PackedInt64Array; + + if (type == typeof(float[])) + return Variant.Type.PackedFloat32Array; + + if (type == typeof(double[])) + return Variant.Type.PackedFloat64Array; + + if (type == typeof(string[])) + return Variant.Type.PackedStringArray; + + if (type == typeof(Vector2[])) + return Variant.Type.PackedVector2Array; + + if (type == typeof(Vector3[])) + return Variant.Type.PackedVector3Array; + + if (type == typeof(Color[])) + return Variant.Type.PackedColorArray; + + if (type == typeof(StringName[])) + return Variant.Type.Array; + + if (type == typeof(NodePath[])) + return Variant.Type.Array; + + if (type == typeof(RID[])) + return Variant.Type.Array; + + if (typeof(Godot.Object[]).IsAssignableFrom(type)) + return Variant.Type.Array; + } + else if (type.IsGenericType) + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + return Variant.Type.Object; + } + else if (type == typeof(Variant)) + { + r_nil_is_variant = true; + return Variant.Type.Nil; + } + else + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + return Variant.Type.Object; + + if (typeof(StringName) == type) + return Variant.Type.StringName; + + if (typeof(NodePath) == type) + return Variant.Type.NodePath; + + if (typeof(RID) == type) + return Variant.Type.Rid; + + if (typeof(Collections.Dictionary) == type) + return Variant.Type.Dictionary; + + if (typeof(Collections.Array) == type) + return Variant.Type.Array; + } + + break; + } + } + + // Unknown + return Variant.Type.Nil; + } + + /* TODO: Reflection and type checking each time is slow. This will be replaced with source generators. */ + public static godot_variant ConvertManagedObjectToVariant(object? p_obj) + { + if (p_obj == null) + return new godot_variant(); + + switch (p_obj) + { + case bool @bool: + return VariantUtils.CreateFromBool(@bool); + case char @char: + return VariantUtils.CreateFromInt(@char); + case sbyte @int8: + return VariantUtils.CreateFromInt(@int8); + case short @int16: + return VariantUtils.CreateFromInt(@int16); + case int @int32: + return VariantUtils.CreateFromInt(@int32); + case long @int64: + return VariantUtils.CreateFromInt(@int64); + case byte @uint8: + return VariantUtils.CreateFromInt(@uint8); + case ushort @uint16: + return VariantUtils.CreateFromInt(@uint16); + case uint @uint32: + return VariantUtils.CreateFromInt(@uint32); + case ulong @uint64: + return VariantUtils.CreateFromInt(@uint64); + case float @float: + return VariantUtils.CreateFromFloat(@float); + case double @double: + return VariantUtils.CreateFromFloat(@double); + case Vector2 @vector2: + return VariantUtils.CreateFromVector2(@vector2); + case Vector2i @vector2i: + return VariantUtils.CreateFromVector2i(@vector2i); + case Rect2 @rect2: + return VariantUtils.CreateFromRect2(@rect2); + case Rect2i @rect2i: + return VariantUtils.CreateFromRect2i(@rect2i); + case Transform2D @transform2D: + return VariantUtils.CreateFromTransform2D(@transform2D); + case Vector3 @vector3: + return VariantUtils.CreateFromVector3(@vector3); + case Vector3i @vector3i: + return VariantUtils.CreateFromVector3i(@vector3i); + case Vector4 @vector4: + return VariantUtils.CreateFromVector4(@vector4); + case Vector4i @vector4i: + return VariantUtils.CreateFromVector4i(@vector4i); + case Basis @basis: + return VariantUtils.CreateFromBasis(@basis); + case Quaternion @quaternion: + return VariantUtils.CreateFromQuaternion(@quaternion); + case Transform3D @transform3d: + return VariantUtils.CreateFromTransform3D(@transform3d); + case Projection @projection: + return VariantUtils.CreateFromProjection(@projection); + case AABB @aabb: + return VariantUtils.CreateFromAABB(@aabb); + case Color @color: + return VariantUtils.CreateFromColor(@color); + case Plane @plane: + return VariantUtils.CreateFromPlane(@plane); + case Callable @callable: + return VariantUtils.CreateFromCallable(@callable); + case SignalInfo @signalInfo: + return VariantUtils.CreateFromSignalInfo(@signalInfo); + case Enum @enum: + return VariantUtils.CreateFromInt(Convert.ToInt64(@enum)); + case string @string: + return VariantUtils.CreateFromString(@string); + case byte[] byteArray: + return VariantUtils.CreateFromPackedByteArray(byteArray); + case int[] int32Array: + return VariantUtils.CreateFromPackedInt32Array(int32Array); + case long[] int64Array: + return VariantUtils.CreateFromPackedInt64Array(int64Array); + case float[] floatArray: + return VariantUtils.CreateFromPackedFloat32Array(floatArray); + case double[] doubleArray: + return VariantUtils.CreateFromPackedFloat64Array(doubleArray); + case string[] stringArray: + return VariantUtils.CreateFromPackedStringArray(stringArray); + case Vector2[] vector2Array: + return VariantUtils.CreateFromPackedVector2Array(vector2Array); + case Vector3[] vector3Array: + return VariantUtils.CreateFromPackedVector3Array(vector3Array); + case Color[] colorArray: + return VariantUtils.CreateFromPackedColorArray(colorArray); + case StringName[] stringNameArray: + return VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); + case NodePath[] nodePathArray: + return VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); + case RID[] ridArray: + return VariantUtils.CreateFromSystemArrayOfRID(ridArray); + case Godot.Object[] godotObjectArray: + return VariantUtils.CreateFromSystemArrayOfGodotObject(godotObjectArray); + case Godot.Object godotObject: + return VariantUtils.CreateFromGodotObject(godotObject); + case StringName stringName: + return VariantUtils.CreateFromStringName(stringName); + case NodePath nodePath: + return VariantUtils.CreateFromNodePath(nodePath); + case RID rid: + return VariantUtils.CreateFromRID(rid); + case Collections.Dictionary godotDictionary: + return VariantUtils.CreateFromDictionary(godotDictionary); + case Collections.Array godotArray: + return VariantUtils.CreateFromArray(godotArray); + case Variant variant: + return NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); + } + + GD.PushError("Attempted to convert an unmarshallable managed type to Variant. Name: '" + + p_obj.GetType().FullName + "."); + return new godot_variant(); + } + + public static object? ConvertVariantToManagedObjectOfType(in godot_variant p_var, Type type) + { + // This function is only needed to set the value of properties. Fields have their own implementation, set_value_from_variant. + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return VariantUtils.ConvertToBool(p_var); + case TypeCode.Char: + return VariantUtils.ConvertToChar(p_var); + case TypeCode.SByte: + return VariantUtils.ConvertToInt8(p_var); + case TypeCode.Int16: + return VariantUtils.ConvertToInt16(p_var); + case TypeCode.Int32: + return VariantUtils.ConvertToInt32(p_var); + case TypeCode.Int64: + return VariantUtils.ConvertToInt64(p_var); + case TypeCode.Byte: + return VariantUtils.ConvertToUInt8(p_var); + case TypeCode.UInt16: + return VariantUtils.ConvertToUInt16(p_var); + case TypeCode.UInt32: + return VariantUtils.ConvertToUInt32(p_var); + case TypeCode.UInt64: + return VariantUtils.ConvertToUInt64(p_var); + case TypeCode.Single: + return VariantUtils.ConvertToFloat32(p_var); + case TypeCode.Double: + return VariantUtils.ConvertToFloat64(p_var); + case TypeCode.String: + return VariantUtils.ConvertToStringObject(p_var); + default: + { + if (type == typeof(Vector2)) + return VariantUtils.ConvertToVector2(p_var); + + if (type == typeof(Vector2i)) + return VariantUtils.ConvertToVector2i(p_var); + + if (type == typeof(Rect2)) + return VariantUtils.ConvertToRect2(p_var); + + if (type == typeof(Rect2i)) + return VariantUtils.ConvertToRect2i(p_var); + + if (type == typeof(Transform2D)) + return VariantUtils.ConvertToTransform2D(p_var); + + if (type == typeof(Vector3)) + return VariantUtils.ConvertToVector3(p_var); + + if (type == typeof(Vector3i)) + return VariantUtils.ConvertToVector3i(p_var); + + if (type == typeof(Vector4)) + return VariantUtils.ConvertToVector4(p_var); + + if (type == typeof(Vector4i)) + return VariantUtils.ConvertToVector4i(p_var); + + if (type == typeof(Basis)) + return VariantUtils.ConvertToBasis(p_var); + + if (type == typeof(Quaternion)) + return VariantUtils.ConvertToQuaternion(p_var); + + if (type == typeof(Transform3D)) + return VariantUtils.ConvertToTransform3D(p_var); + + if (type == typeof(Projection)) + return VariantUtils.ConvertToProjection(p_var); + + if (type == typeof(AABB)) + return VariantUtils.ConvertToAABB(p_var); + + if (type == typeof(Color)) + return VariantUtils.ConvertToColor(p_var); + + if (type == typeof(Plane)) + return VariantUtils.ConvertToPlane(p_var); + + if (type == typeof(Callable)) + return VariantUtils.ConvertToCallableManaged(p_var); + + if (type == typeof(SignalInfo)) + return VariantUtils.ConvertToSignalInfo(p_var); + + if (type.IsEnum) + { + var enumUnderlyingType = type.GetEnumUnderlyingType(); + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + return VariantUtils.ConvertToInt8(p_var); + case TypeCode.Int16: + return VariantUtils.ConvertToInt16(p_var); + case TypeCode.Int32: + return VariantUtils.ConvertToInt32(p_var); + case TypeCode.Int64: + return VariantUtils.ConvertToInt64(p_var); + case TypeCode.Byte: + return VariantUtils.ConvertToUInt8(p_var); + case TypeCode.UInt16: + return VariantUtils.ConvertToUInt16(p_var); + case TypeCode.UInt32: + return VariantUtils.ConvertToUInt32(p_var); + case TypeCode.UInt64: + return VariantUtils.ConvertToUInt64(p_var); + default: + { + GD.PushError( + "Attempted to convert Variant to enum value of unsupported underlying type. Name: " + + type.FullName + " : " + enumUnderlyingType.FullName + "."); + return null; + } + } + } + + if (type.IsArray || type.IsSZArray) + { + return ConvertVariantToSystemArrayOfType(p_var, type); + } + else if (type.IsGenericType) + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + { + var godotObject = VariantUtils.ConvertToGodotObject(p_var); + + if (!type.IsInstanceOfType(godotObject)) + { + GD.PushError("Invalid cast when marshaling Godot.Object type." + + $" `{godotObject.GetType()}` is not assignable to `{type.FullName}`."); + return null; + } + + return godotObject; + } + + return null; + } + else if (type == typeof(Variant)) + { + return Variant.CreateCopyingBorrowed(p_var); + } + + if (ConvertVariantToManagedObjectOfClass(p_var, type, out object? res)) + return res; + + break; + } + } + + GD.PushError("Attempted to convert Variant to unsupported type. Name: " + + type.FullName + "."); + return null; + } + + private static object? ConvertVariantToSystemArrayOfType(in godot_variant p_var, Type type) + { + if (type == typeof(byte[])) + return VariantUtils.ConvertAsPackedByteArrayToSystemArray(p_var); + + if (type == typeof(int[])) + return VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(p_var); + + if (type == typeof(long[])) + return VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(p_var); + + if (type == typeof(float[])) + return VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(p_var); + + if (type == typeof(double[])) + return VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(p_var); + + if (type == typeof(string[])) + return VariantUtils.ConvertAsPackedStringArrayToSystemArray(p_var); + + if (type == typeof(Vector2[])) + return VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(p_var); + + if (type == typeof(Vector3[])) + return VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(p_var); + + if (type == typeof(Color[])) + return VariantUtils.ConvertAsPackedColorArrayToSystemArray(p_var); + + if (type == typeof(StringName[])) + return VariantUtils.ConvertToSystemArrayOfStringName(p_var); + + if (type == typeof(NodePath[])) + return VariantUtils.ConvertToSystemArrayOfNodePath(p_var); + + if (type == typeof(RID[])) + return VariantUtils.ConvertToSystemArrayOfRID(p_var); + + if (typeof(Godot.Object[]).IsAssignableFrom(type)) + return VariantUtils.ConvertToSystemArrayOfGodotObject(p_var, type); + + GD.PushError("Attempted to convert Variant to array of unsupported element type. Name: " + + type.GetElementType()!.FullName + "."); + return null; + } + + private static bool ConvertVariantToManagedObjectOfClass(in godot_variant p_var, Type type, + out object? res) + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + { + if (p_var.Type == Variant.Type.Nil) + { + res = null; + return true; + } + + if (p_var.Type != Variant.Type.Object) + { + GD.PushError("Invalid cast when marshaling Godot.Object type." + + $" Variant type is `{p_var.Type}`; expected `{p_var.Object}`."); + res = null; + return true; + } + + var godotObjectPtr = VariantUtils.ConvertToGodotObjectPtr(p_var); + + if (godotObjectPtr == IntPtr.Zero) + { + res = null; + return true; + } + + var godotObject = InteropUtils.UnmanagedGetManaged(godotObjectPtr); + + if (!type.IsInstanceOfType(godotObject)) + { + GD.PushError("Invalid cast when marshaling Godot.Object type." + + $" `{godotObject.GetType()}` is not assignable to `{type.FullName}`."); + res = null; + return false; + } + + res = godotObject; + return true; + } + + if (typeof(StringName) == type) + { + res = VariantUtils.ConvertToStringNameObject(p_var); + return true; + } + + if (typeof(NodePath) == type) + { + res = VariantUtils.ConvertToNodePathObject(p_var); + return true; + } + + if (typeof(RID) == type) + { + res = VariantUtils.ConvertToRID(p_var); + return true; + } + + if (typeof(Collections.Dictionary) == type) + { + res = VariantUtils.ConvertToDictionaryObject(p_var); + return true; + } + + if (typeof(Collections.Array) == type) + { + res = VariantUtils.ConvertToArrayObject(p_var); + return true; + } + + res = null; + return false; + } + + public static unsafe object? ConvertVariantToManagedObject(in godot_variant p_var) + { + switch (p_var.Type) + { + case Variant.Type.Bool: + return p_var.Bool.ToBool(); + case Variant.Type.Int: + return p_var.Int; + case Variant.Type.Float: + { +#if REAL_T_IS_DOUBLE + return p_var.Float; +#else + return (float)p_var.Float; +#endif + } + case Variant.Type.String: + return ConvertStringToManaged(p_var.String); + case Variant.Type.Vector2: + return p_var.Vector2; + case Variant.Type.Vector2i: + return p_var.Vector2i; + case Variant.Type.Rect2: + return p_var.Rect2; + case Variant.Type.Rect2i: + return p_var.Rect2i; + case Variant.Type.Vector3: + return p_var.Vector3; + case Variant.Type.Vector3i: + return p_var.Vector3i; + case Variant.Type.Transform2d: + return *p_var.Transform2D; + case Variant.Type.Vector4: + return *p_var.Vector4; + case Variant.Type.Vector4i: + return *p_var.Vector4i; + case Variant.Type.Plane: + return p_var.Plane; + case Variant.Type.Quaternion: + return p_var.Quaternion; + case Variant.Type.Aabb: + return *p_var.AABB; + case Variant.Type.Basis: + return *p_var.Basis; + case Variant.Type.Transform3d: + return *p_var.Transform3D; + case Variant.Type.Projection: + return *p_var.Projection; + case Variant.Type.Color: + return p_var.Color; + case Variant.Type.StringName: + { + // The Variant owns the value, so we need to make a copy + return StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(p_var.StringName)); + } + case Variant.Type.NodePath: + { + // The Variant owns the value, so we need to make a copy + return NodePath.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_node_path_new_copy(p_var.NodePath)); + } + case Variant.Type.Rid: + return p_var.RID; + case Variant.Type.Object: + return InteropUtils.UnmanagedGetManaged(p_var.Object); + case Variant.Type.Callable: + return ConvertCallableToManaged(p_var.Callable); + case Variant.Type.Signal: + return ConvertSignalToManaged(p_var.Signal); + case Variant.Type.Dictionary: + { + // The Variant owns the value, so we need to make a copy + return Collections.Dictionary.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary)); + } + case Variant.Type.Array: + { + // The Variant owns the value, so we need to make a copy + return Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_var.Array)); + } + case Variant.Type.PackedByteArray: + return VariantUtils.ConvertAsPackedByteArrayToSystemArray(p_var); + case Variant.Type.PackedInt32Array: + return VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(p_var); + case Variant.Type.PackedInt64Array: + return VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(p_var); + case Variant.Type.PackedFloat32Array: + return VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(p_var); + case Variant.Type.PackedFloat64Array: + return VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(p_var); + case Variant.Type.PackedStringArray: + return VariantUtils.ConvertAsPackedStringArrayToSystemArray(p_var); + case Variant.Type.PackedVector2Array: + return VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(p_var); + case Variant.Type.PackedVector3Array: + return VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(p_var); + case Variant.Type.PackedColorArray: + return VariantUtils.ConvertAsPackedColorArrayToSystemArray(p_var); + default: + return null; + } + } + + // String + + public static unsafe godot_string ConvertStringToNative(string? p_mono_string) + { + if (p_mono_string == null) + return new godot_string(); + + fixed (char* methodChars = p_mono_string) + { + NativeFuncs.godotsharp_string_new_with_utf16_chars(out godot_string dest, methodChars); + return dest; + } + } + + public static unsafe string ConvertStringToManaged(in godot_string p_string) + { + if (p_string.Buffer == IntPtr.Zero) + return string.Empty; + + const int sizeOfChar32 = 4; + byte* bytes = (byte*)p_string.Buffer; + int size = p_string.Size; + if (size == 0) + return string.Empty; + size -= 1; // zero at the end + int sizeInBytes = size * sizeOfChar32; + return System.Text.Encoding.UTF32.GetString(bytes, sizeInBytes); + } + + // Callable + + public static godot_callable ConvertCallableToNative(in Callable p_managed_callable) + { + if (p_managed_callable.Delegate != null) + { + var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate); + NativeFuncs.godotsharp_callable_new_with_delegate( + GCHandle.ToIntPtr(gcHandle), out godot_callable callable); + return callable; + } + else + { + godot_string_name method; + + if (p_managed_callable.Method != null && !p_managed_callable.Method.IsEmpty) + { + var src = (godot_string_name)p_managed_callable.Method.NativeValue; + method = NativeFuncs.godotsharp_string_name_new_copy(src); + } + else + { + method = default; + } + + return new godot_callable(method /* Takes ownership of disposable */, + p_managed_callable.Target.GetInstanceId()); + } + } + + public static Callable ConvertCallableToManaged(in godot_callable p_callable) + { + if (NativeFuncs.godotsharp_callable_get_data_for_marshalling(p_callable, + out IntPtr delegateGCHandle, out IntPtr godotObject, + out godot_string_name name).ToBool()) + { + if (delegateGCHandle != IntPtr.Zero) + { + return new Callable((Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target); + } + else + { + return new Callable( + InteropUtils.UnmanagedGetManaged(godotObject), + StringName.CreateTakingOwnershipOfDisposableValue(name)); + } + } + + // Some other unsupported callable + return new Callable(); + } + + // SignalInfo + + public static godot_signal ConvertSignalToNative(in SignalInfo p_managed_signal) + { + ulong ownerId = p_managed_signal.Owner.GetInstanceId(); + godot_string_name name; + + if (p_managed_signal.Name != null && !p_managed_signal.Name.IsEmpty) + { + var src = (godot_string_name)p_managed_signal.Name.NativeValue; + name = NativeFuncs.godotsharp_string_name_new_copy(src); + } + else + { + name = default; + } + + return new godot_signal(name, ownerId); + } + + public static SignalInfo ConvertSignalToManaged(in godot_signal p_signal) + { + var owner = GD.InstanceFromId(p_signal.ObjectId); + var name = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(p_signal.Name)); + return new SignalInfo(owner, name); + } + + // Array + + internal static T[] ConvertNativeGodotArrayToSystemArrayOfGodotObjectType<T>(in godot_array p_array) + where T : Godot.Object + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new T[length]; + + for (int i = 0; i < length; i++) + ret[i] = (T)array[i].AsGodotObject(); + + return ret; + } + + // TODO: This needs reflection. Look for an alternative. + internal static Godot.Object[] ConvertNativeGodotArrayToSystemArrayOfGodotObjectType(in godot_array p_array, + Type type) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = (Godot.Object[])Activator.CreateInstance(type, length)!; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsGodotObject(); + + return ret; + } + + internal static StringName[] ConvertNativeGodotArrayToSystemArrayOfStringName(in godot_array p_array) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new StringName[length]; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsStringName(); + + return ret; + } + + internal static NodePath[] ConvertNativeGodotArrayToSystemArrayOfNodePath(in godot_array p_array) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new NodePath[length]; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsNodePath(); + + return ret; + } + + internal static RID[] ConvertNativeGodotArrayToSystemArrayOfRID(in godot_array p_array) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new RID[length]; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsRID(); + + return ret; + } + + // PackedByteArray + + public static unsafe byte[] ConvertNativePackedByteArrayToSystemArray(in godot_packed_byte_array p_array) + { + byte* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<byte>(); + var array = new byte[size]; + fixed (byte* dest = array) + Buffer.MemoryCopy(buffer, dest, size, size); + return array; + } + + public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_byte_array(); + fixed (byte* src = p_array) + return NativeFuncs.godotsharp_packed_byte_array_new_mem_copy(src, p_array.Length); + } + + // PackedInt32Array + + public static unsafe int[] ConvertNativePackedInt32ArrayToSystemArray(godot_packed_int32_array p_array) + { + int* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<int>(); + int sizeInBytes = size * sizeof(int); + var array = new int[size]; + fixed (int* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_int32_array(); + fixed (int* src = p_array) + return NativeFuncs.godotsharp_packed_int32_array_new_mem_copy(src, p_array.Length); + } + + // PackedInt64Array + + public static unsafe long[] ConvertNativePackedInt64ArrayToSystemArray(godot_packed_int64_array p_array) + { + long* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<long>(); + int sizeInBytes = size * sizeof(long); + var array = new long[size]; + fixed (long* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_int64_array(); + fixed (long* src = p_array) + return NativeFuncs.godotsharp_packed_int64_array_new_mem_copy(src, p_array.Length); + } + + // PackedFloat32Array + + public static unsafe float[] ConvertNativePackedFloat32ArrayToSystemArray(godot_packed_float32_array p_array) + { + float* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<float>(); + int sizeInBytes = size * sizeof(float); + var array = new float[size]; + fixed (float* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array( + Span<float> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_float32_array(); + fixed (float* src = p_array) + return NativeFuncs.godotsharp_packed_float32_array_new_mem_copy(src, p_array.Length); + } + + // PackedFloat64Array + + public static unsafe double[] ConvertNativePackedFloat64ArrayToSystemArray(godot_packed_float64_array p_array) + { + double* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<double>(); + int sizeInBytes = size * sizeof(double); + var array = new double[size]; + fixed (double* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array( + Span<double> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_float64_array(); + fixed (double* src = p_array) + return NativeFuncs.godotsharp_packed_float64_array_new_mem_copy(src, p_array.Length); + } + + // PackedStringArray + + public static unsafe string[] ConvertNativePackedStringArrayToSystemArray(godot_packed_string_array p_array) + { + godot_string* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<string>(); + var array = new string[size]; + for (int i = 0; i < size; i++) + array[i] = ConvertStringToManaged(buffer[i]); + return array; + } + + public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(Span<string> p_array) + { + godot_packed_string_array dest = new godot_packed_string_array(); + + if (p_array.IsEmpty) + return dest; + + /* TODO: Replace godotsharp_packed_string_array_add with a single internal call to + get the write address. We can't use `dest._ptr` directly for writing due to COW. */ + + for (int i = 0; i < p_array.Length; i++) + { + using godot_string godotStrElem = ConvertStringToNative(p_array[i]); + NativeFuncs.godotsharp_packed_string_array_add(ref dest, godotStrElem); + } + + return dest; + } + + // PackedVector2Array + + public static unsafe Vector2[] ConvertNativePackedVector2ArrayToSystemArray(godot_packed_vector2_array p_array) + { + Vector2* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<Vector2>(); + int sizeInBytes = size * sizeof(Vector2); + var array = new Vector2[size]; + fixed (Vector2* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array( + Span<Vector2> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_vector2_array(); + fixed (Vector2* src = p_array) + return NativeFuncs.godotsharp_packed_vector2_array_new_mem_copy(src, p_array.Length); + } + + // PackedVector3Array + + public static unsafe Vector3[] ConvertNativePackedVector3ArrayToSystemArray(godot_packed_vector3_array p_array) + { + Vector3* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<Vector3>(); + int sizeInBytes = size * sizeof(Vector3); + var array = new Vector3[size]; + fixed (Vector3* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array( + Span<Vector3> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_vector3_array(); + fixed (Vector3* src = p_array) + return NativeFuncs.godotsharp_packed_vector3_array_new_mem_copy(src, p_array.Length); + } + + // PackedColorArray + + public static unsafe Color[] ConvertNativePackedColorArrayToSystemArray(godot_packed_color_array p_array) + { + Color* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<Color>(); + int sizeInBytes = size * sizeof(Color); + var array = new Color[size]; + fixed (Color* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_color_array(); + fixed (Color* src = p_array) + return NativeFuncs.godotsharp_packed_color_array_new_mem_copy(src, p_array.Length); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs new file mode 100644 index 0000000000..6d2534e6f7 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -0,0 +1,522 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Godot.SourceGenerators.Internal; + +// ReSharper disable InconsistentNaming + +namespace Godot.NativeInterop +{ + /* + * IMPORTANT: + * The order of the methods defined in NativeFuncs must match the order + * in the array defined at the bottom of 'glue/runtime_interop.cpp'. + */ + + [GenerateUnmanagedCallbacks(typeof(UnmanagedCallbacks))] + public static unsafe partial class NativeFuncs + { + private static bool initialized = false; + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Global + public static void Initialize(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + if (initialized) + throw new InvalidOperationException("Already initialized"); + initialized = true; + + if (unmanagedCallbacksSize != sizeof(UnmanagedCallbacks)) + throw new ArgumentException("Unmanaged callbacks size mismatch"); + + _unmanagedCallbacks = Unsafe.AsRef<UnmanagedCallbacks>((void*)unmanagedCallbacks); + } + + private partial struct UnmanagedCallbacks + { + } + + // Custom functions + + public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname, + in godot_string_name p_methodname); + + public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor( + in godot_string_name p_classname); + + public static partial IntPtr godotsharp_engine_get_singleton(in godot_string p_name); + + + internal static partial Error godotsharp_stack_info_vector_resize( + ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector, int p_size); + + internal static partial void godotsharp_stack_info_vector_destroy( + ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector); + + internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func, + in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr, + godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector); + + internal static partial bool godotsharp_internal_script_debugger_is_active(); + + internal static partial IntPtr godotsharp_internal_object_get_associated_gchandle(IntPtr ptr); + + internal static partial void godotsharp_internal_object_disposed(IntPtr ptr, IntPtr gcHandleToFree); + + internal static partial void godotsharp_internal_refcounted_disposed(IntPtr ptr, IntPtr gcHandleToFree, + godot_bool isFinalizer); + + internal static partial Error godotsharp_internal_signal_awaiter_connect(IntPtr source, + in godot_string_name signal, + IntPtr target, IntPtr awaiterHandlePtr); + + internal static partial void godotsharp_internal_tie_native_managed_to_unmanaged(IntPtr gcHandleIntPtr, + IntPtr unmanaged, in godot_string_name nativeName, godot_bool refCounted); + + internal static partial void godotsharp_internal_tie_user_managed_to_unmanaged(IntPtr gcHandleIntPtr, + IntPtr unmanaged, godot_ref* scriptPtr, godot_bool refCounted); + + internal static partial void godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup( + IntPtr gcHandleIntPtr, IntPtr unmanaged); + + internal static partial IntPtr godotsharp_internal_unmanaged_get_script_instance_managed(IntPtr p_unmanaged, + out godot_bool r_has_cs_script_instance); + + internal static partial IntPtr godotsharp_internal_unmanaged_get_instance_binding_managed(IntPtr p_unmanaged); + + internal static partial IntPtr godotsharp_internal_unmanaged_instance_binding_create_managed(IntPtr p_unmanaged, + IntPtr oldGCHandlePtr); + + internal static partial void godotsharp_internal_new_csharp_script(godot_ref* r_dest); + + internal static partial godot_bool godotsharp_internal_script_load(in godot_string p_path, godot_ref* r_dest); + + internal static partial void godotsharp_internal_reload_registered_script(IntPtr scriptPtr); + + internal static partial void godotsharp_array_filter_godot_objects_by_native(in godot_string_name p_native_name, + in godot_array p_input, out godot_array r_output); + + internal static partial void godotsharp_array_filter_godot_objects_by_non_native(in godot_array p_input, + out godot_array r_output); + + public static partial void godotsharp_ref_new_from_ref_counted_ptr(out godot_ref r_dest, + IntPtr p_ref_counted_ptr); + + public static partial void godotsharp_ref_destroy(ref godot_ref p_instance); + + public static partial void godotsharp_string_name_new_from_string(out godot_string_name r_dest, + in godot_string p_name); + + public static partial void godotsharp_node_path_new_from_string(out godot_node_path r_dest, + in godot_string p_name); + + public static partial void + godotsharp_string_name_as_string(out godot_string r_dest, in godot_string_name p_name); + + public static partial void godotsharp_node_path_as_string(out godot_string r_dest, in godot_node_path p_np); + + public static partial godot_packed_byte_array godotsharp_packed_byte_array_new_mem_copy(byte* p_src, + int p_length); + + public static partial godot_packed_int32_array godotsharp_packed_int32_array_new_mem_copy(int* p_src, + int p_length); + + public static partial godot_packed_int64_array godotsharp_packed_int64_array_new_mem_copy(long* p_src, + int p_length); + + public static partial godot_packed_float32_array godotsharp_packed_float32_array_new_mem_copy(float* p_src, + int p_length); + + public static partial godot_packed_float64_array godotsharp_packed_float64_array_new_mem_copy(double* p_src, + int p_length); + + public static partial godot_packed_vector2_array godotsharp_packed_vector2_array_new_mem_copy(Vector2* p_src, + int p_length); + + public static partial godot_packed_vector3_array godotsharp_packed_vector3_array_new_mem_copy(Vector3* p_src, + int p_length); + + public static partial godot_packed_color_array godotsharp_packed_color_array_new_mem_copy(Color* p_src, + int p_length); + + public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest, + in godot_string p_element); + + public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, + out godot_callable r_callable); + + internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable, + out IntPtr r_delegate_handle, out IntPtr r_object, out godot_string_name r_name); + + internal static partial godot_variant godotsharp_callable_call(in godot_callable p_callable, + godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error); + + internal static partial void godotsharp_callable_call_deferred(in godot_callable p_callable, + godot_variant** p_args, int p_arg_count); + + // GDNative functions + + // gdnative.h + + public static partial void godotsharp_method_bind_ptrcall(IntPtr p_method_bind, IntPtr p_instance, void** p_args, + void* p_ret); + + public static partial godot_variant godotsharp_method_bind_call(IntPtr p_method_bind, IntPtr p_instance, + godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error); + + // variant.h + + public static partial void + godotsharp_variant_new_string_name(out godot_variant r_dest, in godot_string_name p_s); + + public static partial void godotsharp_variant_new_copy(out godot_variant r_dest, in godot_variant p_src); + + public static partial void godotsharp_variant_new_node_path(out godot_variant r_dest, in godot_node_path p_np); + + public static partial void godotsharp_variant_new_object(out godot_variant r_dest, IntPtr p_obj); + + public static partial void godotsharp_variant_new_transform2d(out godot_variant r_dest, in Transform2D p_t2d); + + public static partial void godotsharp_variant_new_vector4(out godot_variant r_dest, in Vector4 p_vec4); + + public static partial void godotsharp_variant_new_vector4i(out godot_variant r_dest, in Vector4i p_vec4i); + + public static partial void godotsharp_variant_new_basis(out godot_variant r_dest, in Basis p_basis); + + public static partial void godotsharp_variant_new_transform3d(out godot_variant r_dest, in Transform3D p_trans); + + public static partial void godotsharp_variant_new_projection(out godot_variant r_dest, in Projection p_proj); + + public static partial void godotsharp_variant_new_aabb(out godot_variant r_dest, in AABB p_aabb); + + public static partial void godotsharp_variant_new_dictionary(out godot_variant r_dest, + in godot_dictionary p_dict); + + public static partial void godotsharp_variant_new_array(out godot_variant r_dest, in godot_array p_arr); + + public static partial void godotsharp_variant_new_packed_byte_array(out godot_variant r_dest, + in godot_packed_byte_array p_pba); + + public static partial void godotsharp_variant_new_packed_int32_array(out godot_variant r_dest, + in godot_packed_int32_array p_pia); + + public static partial void godotsharp_variant_new_packed_int64_array(out godot_variant r_dest, + in godot_packed_int64_array p_pia); + + public static partial void godotsharp_variant_new_packed_float32_array(out godot_variant r_dest, + in godot_packed_float32_array p_pra); + + public static partial void godotsharp_variant_new_packed_float64_array(out godot_variant r_dest, + in godot_packed_float64_array p_pra); + + public static partial void godotsharp_variant_new_packed_string_array(out godot_variant r_dest, + in godot_packed_string_array p_psa); + + public static partial void godotsharp_variant_new_packed_vector2_array(out godot_variant r_dest, + in godot_packed_vector2_array p_pv2a); + + public static partial void godotsharp_variant_new_packed_vector3_array(out godot_variant r_dest, + in godot_packed_vector3_array p_pv3a); + + public static partial void godotsharp_variant_new_packed_color_array(out godot_variant r_dest, + in godot_packed_color_array p_pca); + + public static partial godot_bool godotsharp_variant_as_bool(in godot_variant p_self); + + public static partial Int64 godotsharp_variant_as_int(in godot_variant p_self); + + public static partial double godotsharp_variant_as_float(in godot_variant p_self); + + public static partial godot_string godotsharp_variant_as_string(in godot_variant p_self); + + public static partial Vector2 godotsharp_variant_as_vector2(in godot_variant p_self); + + public static partial Vector2i godotsharp_variant_as_vector2i(in godot_variant p_self); + + public static partial Rect2 godotsharp_variant_as_rect2(in godot_variant p_self); + + public static partial Rect2i godotsharp_variant_as_rect2i(in godot_variant p_self); + + public static partial Vector3 godotsharp_variant_as_vector3(in godot_variant p_self); + + public static partial Vector3i godotsharp_variant_as_vector3i(in godot_variant p_self); + + public static partial Transform2D godotsharp_variant_as_transform2d(in godot_variant p_self); + + public static partial Vector4 godotsharp_variant_as_vector4(in godot_variant p_self); + + public static partial Vector4i godotsharp_variant_as_vector4i(in godot_variant p_self); + + public static partial Plane godotsharp_variant_as_plane(in godot_variant p_self); + + public static partial Quaternion godotsharp_variant_as_quaternion(in godot_variant p_self); + + public static partial AABB godotsharp_variant_as_aabb(in godot_variant p_self); + + public static partial Basis godotsharp_variant_as_basis(in godot_variant p_self); + + public static partial Transform3D godotsharp_variant_as_transform3d(in godot_variant p_self); + + public static partial Projection godotsharp_variant_as_projection(in godot_variant p_self); + + public static partial Color godotsharp_variant_as_color(in godot_variant p_self); + + public static partial godot_string_name godotsharp_variant_as_string_name(in godot_variant p_self); + + public static partial godot_node_path godotsharp_variant_as_node_path(in godot_variant p_self); + + public static partial RID godotsharp_variant_as_rid(in godot_variant p_self); + + public static partial godot_callable godotsharp_variant_as_callable(in godot_variant p_self); + + public static partial godot_signal godotsharp_variant_as_signal(in godot_variant p_self); + + public static partial godot_dictionary godotsharp_variant_as_dictionary(in godot_variant p_self); + + public static partial godot_array godotsharp_variant_as_array(in godot_variant p_self); + + public static partial godot_packed_byte_array godotsharp_variant_as_packed_byte_array(in godot_variant p_self); + + public static partial godot_packed_int32_array godotsharp_variant_as_packed_int32_array(in godot_variant p_self); + + public static partial godot_packed_int64_array godotsharp_variant_as_packed_int64_array(in godot_variant p_self); + + public static partial godot_packed_float32_array godotsharp_variant_as_packed_float32_array( + in godot_variant p_self); + + public static partial godot_packed_float64_array godotsharp_variant_as_packed_float64_array( + in godot_variant p_self); + + public static partial godot_packed_string_array godotsharp_variant_as_packed_string_array( + in godot_variant p_self); + + public static partial godot_packed_vector2_array godotsharp_variant_as_packed_vector2_array( + in godot_variant p_self); + + public static partial godot_packed_vector3_array godotsharp_variant_as_packed_vector3_array( + in godot_variant p_self); + + public static partial godot_packed_color_array godotsharp_variant_as_packed_color_array(in godot_variant p_self); + + public static partial godot_bool godotsharp_variant_equals(in godot_variant p_a, in godot_variant p_b); + + // string.h + + public static partial void godotsharp_string_new_with_utf16_chars(out godot_string r_dest, char* p_contents); + + // string_name.h + + public static partial void godotsharp_string_name_new_copy(out godot_string_name r_dest, + in godot_string_name p_src); + + // node_path.h + + public static partial void godotsharp_node_path_new_copy(out godot_node_path r_dest, in godot_node_path p_src); + + // array.h + + public static partial void godotsharp_array_new(out godot_array r_dest); + + public static partial void godotsharp_array_new_copy(out godot_array r_dest, in godot_array p_src); + + public static partial godot_variant* godotsharp_array_ptrw(ref godot_array p_self); + + // dictionary.h + + public static partial void godotsharp_dictionary_new(out godot_dictionary r_dest); + + public static partial void godotsharp_dictionary_new_copy(out godot_dictionary r_dest, + in godot_dictionary p_src); + + // destroy functions + + public static partial void godotsharp_packed_byte_array_destroy(ref godot_packed_byte_array p_self); + + public static partial void godotsharp_packed_int32_array_destroy(ref godot_packed_int32_array p_self); + + public static partial void godotsharp_packed_int64_array_destroy(ref godot_packed_int64_array p_self); + + public static partial void godotsharp_packed_float32_array_destroy(ref godot_packed_float32_array p_self); + + public static partial void godotsharp_packed_float64_array_destroy(ref godot_packed_float64_array p_self); + + public static partial void godotsharp_packed_string_array_destroy(ref godot_packed_string_array p_self); + + public static partial void godotsharp_packed_vector2_array_destroy(ref godot_packed_vector2_array p_self); + + public static partial void godotsharp_packed_vector3_array_destroy(ref godot_packed_vector3_array p_self); + + public static partial void godotsharp_packed_color_array_destroy(ref godot_packed_color_array p_self); + + public static partial void godotsharp_variant_destroy(ref godot_variant p_self); + + public static partial void godotsharp_string_destroy(ref godot_string p_self); + + public static partial void godotsharp_string_name_destroy(ref godot_string_name p_self); + + public static partial void godotsharp_node_path_destroy(ref godot_node_path p_self); + + public static partial void godotsharp_signal_destroy(ref godot_signal p_self); + + public static partial void godotsharp_callable_destroy(ref godot_callable p_self); + + public static partial void godotsharp_array_destroy(ref godot_array p_self); + + public static partial void godotsharp_dictionary_destroy(ref godot_dictionary p_self); + + // Array + + public static partial int godotsharp_array_add(ref godot_array p_self, in godot_variant p_item); + + public static partial void + godotsharp_array_duplicate(ref godot_array p_self, godot_bool p_deep, out godot_array r_dest); + + public static partial int godotsharp_array_index_of(ref godot_array p_self, in godot_variant p_item); + + public static partial void godotsharp_array_insert(ref godot_array p_self, int p_index, in godot_variant p_item); + + public static partial void godotsharp_array_remove_at(ref godot_array p_self, int p_index); + + public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size); + + public static partial Error godotsharp_array_shuffle(ref godot_array p_self); + + public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str); + + // Dictionary + + public static partial godot_bool godotsharp_dictionary_try_get_value(ref godot_dictionary p_self, + in godot_variant p_key, + out godot_variant r_value); + + public static partial void godotsharp_dictionary_set_value(ref godot_dictionary p_self, in godot_variant p_key, + in godot_variant p_value); + + public static partial void godotsharp_dictionary_keys(ref godot_dictionary p_self, out godot_array r_dest); + + public static partial void godotsharp_dictionary_values(ref godot_dictionary p_self, out godot_array r_dest); + + public static partial int godotsharp_dictionary_count(ref godot_dictionary p_self); + + public static partial void godotsharp_dictionary_key_value_pair_at(ref godot_dictionary p_self, int p_index, + out godot_variant r_key, out godot_variant r_value); + + public static partial void godotsharp_dictionary_add(ref godot_dictionary p_self, in godot_variant p_key, + in godot_variant p_value); + + public static partial void godotsharp_dictionary_clear(ref godot_dictionary p_self); + + public static partial godot_bool godotsharp_dictionary_contains_key(ref godot_dictionary p_self, + in godot_variant p_key); + + public static partial void godotsharp_dictionary_duplicate(ref godot_dictionary p_self, godot_bool p_deep, + out godot_dictionary r_dest); + + public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dictionary p_self, + in godot_variant p_key); + + public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str); + + // StringExtensions + + public static partial void godotsharp_string_md5_buffer(in godot_string p_self, + out godot_packed_byte_array r_md5_buffer); + + public static partial void godotsharp_string_md5_text(in godot_string p_self, out godot_string r_md5_text); + + public static partial int godotsharp_string_rfind(in godot_string p_self, in godot_string p_what, int p_from); + + public static partial int godotsharp_string_rfindn(in godot_string p_self, in godot_string p_what, int p_from); + + public static partial void godotsharp_string_sha256_buffer(in godot_string p_self, + out godot_packed_byte_array r_sha256_buffer); + + public static partial void godotsharp_string_sha256_text(in godot_string p_self, + out godot_string r_sha256_text); + + public static partial void godotsharp_string_simplify_path(in godot_string p_self, + out godot_string r_simplified_path); + + // NodePath + + public static partial void godotsharp_node_path_get_as_property_path(in godot_node_path p_self, + ref godot_node_path r_dest); + + public static partial void godotsharp_node_path_get_concatenated_names(in godot_node_path p_self, + out godot_string r_names); + + public static partial void godotsharp_node_path_get_concatenated_subnames(in godot_node_path p_self, + out godot_string r_subnames); + + public static partial void godotsharp_node_path_get_name(in godot_node_path p_self, int p_idx, + out godot_string r_name); + + public static partial int godotsharp_node_path_get_name_count(in godot_node_path p_self); + + public static partial void godotsharp_node_path_get_subname(in godot_node_path p_self, int p_idx, + out godot_string r_subname); + + public static partial int godotsharp_node_path_get_subname_count(in godot_node_path p_self); + + public static partial godot_bool godotsharp_node_path_is_absolute(in godot_node_path p_self); + + // GD, etc + + internal static partial void godotsharp_bytes2var(in godot_packed_byte_array p_bytes, + godot_bool p_allow_objects, + out godot_variant r_ret); + + internal static partial void godotsharp_convert(in godot_variant p_what, int p_type, + out godot_variant r_ret); + + internal static partial int godotsharp_hash(in godot_variant p_var); + + internal static partial IntPtr godotsharp_instance_from_id(ulong p_instance_id); + + internal static partial void godotsharp_print(in godot_string p_what); + + public static partial void godotsharp_print_rich(in godot_string p_what); + + internal static partial void godotsharp_printerr(in godot_string p_what); + + internal static partial void godotsharp_printraw(in godot_string p_what); + + internal static partial void godotsharp_prints(in godot_string p_what); + + internal static partial void godotsharp_printt(in godot_string p_what); + + internal static partial float godotsharp_randf(); + + internal static partial uint godotsharp_randi(); + + internal static partial void godotsharp_randomize(); + + internal static partial double godotsharp_randf_range(double from, double to); + + internal static partial double godotsharp_randfn(double mean, double deviation); + + internal static partial int godotsharp_randi_range(int from, int to); + + internal static partial uint godotsharp_rand_from_seed(ulong seed, out ulong newSeed); + + internal static partial void godotsharp_seed(ulong seed); + + internal static partial void godotsharp_weakref(IntPtr p_obj, out godot_ref r_weak_ref); + + internal static partial void godotsharp_str(in godot_array p_what, out godot_string r_ret); + + internal static partial void godotsharp_str2var(in godot_string p_str, out godot_variant r_ret); + + internal static partial void godotsharp_var2bytes(in godot_variant p_what, godot_bool p_full_objects, + out godot_packed_byte_array r_bytes); + + internal static partial void godotsharp_var2str(in godot_variant p_var, out godot_string r_ret); + + internal static partial void godotsharp_pusherror(in godot_string p_str); + + internal static partial void godotsharp_pushwarning(in godot_string p_str); + + // Object + + public static partial void godotsharp_object_to_string(IntPtr ptr, out godot_string r_str); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs new file mode 100644 index 0000000000..2ea3c18d26 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs @@ -0,0 +1,99 @@ +// ReSharper disable InconsistentNaming + +namespace Godot.NativeInterop +{ + public static partial class NativeFuncs + { + public static godot_variant godotsharp_variant_new_copy(in godot_variant src) + { + switch (src.Type) + { + case Variant.Type.Nil: + return default; + case Variant.Type.Bool: + return new godot_variant() { Bool = src.Bool }; + case Variant.Type.Int: + return new godot_variant() { Int = src.Int }; + case Variant.Type.Float: + return new godot_variant() { Float = src.Float }; + case Variant.Type.Vector2: + return new godot_variant() { Vector2 = src.Vector2 }; + case Variant.Type.Vector2i: + return new godot_variant() { Vector2i = src.Vector2i }; + case Variant.Type.Rect2: + return new godot_variant() { Rect2 = src.Rect2 }; + case Variant.Type.Rect2i: + return new godot_variant() { Rect2i = src.Rect2i }; + case Variant.Type.Vector3: + return new godot_variant() { Vector3 = src.Vector3 }; + case Variant.Type.Vector3i: + return new godot_variant() { Vector3i = src.Vector3i }; + case Variant.Type.Plane: + return new godot_variant() { Plane = src.Plane }; + case Variant.Type.Quaternion: + return new godot_variant() { Quaternion = src.Quaternion }; + case Variant.Type.Color: + return new godot_variant() { Color = src.Color }; + case Variant.Type.Rid: + return new godot_variant() { RID = src.RID }; + } + + godotsharp_variant_new_copy(out godot_variant ret, src); + return ret; + } + + public static godot_string_name godotsharp_string_name_new_copy(in godot_string_name src) + { + if (src.IsEmpty) + return default; + godotsharp_string_name_new_copy(out godot_string_name ret, src); + return ret; + } + + public static godot_node_path godotsharp_node_path_new_copy(in godot_node_path src) + { + if (src.IsEmpty) + return default; + godotsharp_node_path_new_copy(out godot_node_path ret, src); + return ret; + } + + public static godot_array godotsharp_array_new() + { + godotsharp_array_new(out godot_array ret); + return ret; + } + + public static godot_array godotsharp_array_new_copy(in godot_array src) + { + godotsharp_array_new_copy(out godot_array ret, src); + return ret; + } + + public static godot_dictionary godotsharp_dictionary_new() + { + godotsharp_dictionary_new(out godot_dictionary ret); + return ret; + } + + public static godot_dictionary godotsharp_dictionary_new_copy(in godot_dictionary src) + { + godotsharp_dictionary_new_copy(out godot_dictionary ret, src); + return ret; + } + + public static godot_string_name godotsharp_string_name_new_from_string(string name) + { + using godot_string src = Marshaling.ConvertStringToNative(name); + godotsharp_string_name_new_from_string(out godot_string_name ret, src); + return ret; + } + + public static godot_node_path godotsharp_node_path_new_from_string(string name) + { + using godot_string src = Marshaling.ConvertStringToNative(name); + godotsharp_node_path_new_from_string(out godot_node_path ret, src); + return ret; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs new file mode 100644 index 0000000000..422df74c23 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs @@ -0,0 +1,20 @@ +using System.Runtime.CompilerServices; + +namespace Godot.NativeInterop +{ + // Our source generators will add trampolines methods that access variant arguments. + // This struct makes that possible without having to enable `AllowUnsafeBlocks` in game projects. + + public unsafe ref struct NativeVariantPtrArgs + { + private godot_variant** _args; + + internal NativeVariantPtrArgs(godot_variant** args) => _args = args; + + public ref godot_variant this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref *_args[index]; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs new file mode 100644 index 0000000000..2b5bf2e142 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs @@ -0,0 +1,976 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Godot.NativeInterop; + +internal static unsafe class VariantConversionCallbacks +{ + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + internal static delegate* <in T, godot_variant> GetToVariantCallback<T>() + { + static godot_variant FromBool(in bool @bool) => + VariantUtils.CreateFromBool(@bool); + + static godot_variant FromChar(in char @char) => + VariantUtils.CreateFromInt(@char); + + static godot_variant FromInt8(in sbyte @int8) => + VariantUtils.CreateFromInt(@int8); + + static godot_variant FromInt16(in short @int16) => + VariantUtils.CreateFromInt(@int16); + + static godot_variant FromInt32(in int @int32) => + VariantUtils.CreateFromInt(@int32); + + static godot_variant FromInt64(in long @int64) => + VariantUtils.CreateFromInt(@int64); + + static godot_variant FromUInt8(in byte @uint8) => + VariantUtils.CreateFromInt(@uint8); + + static godot_variant FromUInt16(in ushort @uint16) => + VariantUtils.CreateFromInt(@uint16); + + static godot_variant FromUInt32(in uint @uint32) => + VariantUtils.CreateFromInt(@uint32); + + static godot_variant FromUInt64(in ulong @uint64) => + VariantUtils.CreateFromInt(@uint64); + + static godot_variant FromFloat(in float @float) => + VariantUtils.CreateFromFloat(@float); + + static godot_variant FromDouble(in double @double) => + VariantUtils.CreateFromFloat(@double); + + static godot_variant FromVector2(in Vector2 @vector2) => + VariantUtils.CreateFromVector2(@vector2); + + static godot_variant FromVector2I(in Vector2i vector2I) => + VariantUtils.CreateFromVector2i(vector2I); + + static godot_variant FromRect2(in Rect2 @rect2) => + VariantUtils.CreateFromRect2(@rect2); + + static godot_variant FromRect2I(in Rect2i rect2I) => + VariantUtils.CreateFromRect2i(rect2I); + + static godot_variant FromTransform2D(in Transform2D @transform2D) => + VariantUtils.CreateFromTransform2D(@transform2D); + + static godot_variant FromVector3(in Vector3 @vector3) => + VariantUtils.CreateFromVector3(@vector3); + + static godot_variant FromVector3I(in Vector3i vector3I) => + VariantUtils.CreateFromVector3i(vector3I); + + static godot_variant FromBasis(in Basis @basis) => + VariantUtils.CreateFromBasis(@basis); + + static godot_variant FromQuaternion(in Quaternion @quaternion) => + VariantUtils.CreateFromQuaternion(@quaternion); + + static godot_variant FromTransform3D(in Transform3D @transform3d) => + VariantUtils.CreateFromTransform3D(@transform3d); + + static godot_variant FromAabb(in AABB @aabb) => + VariantUtils.CreateFromAABB(@aabb); + + static godot_variant FromColor(in Color @color) => + VariantUtils.CreateFromColor(@color); + + static godot_variant FromPlane(in Plane @plane) => + VariantUtils.CreateFromPlane(@plane); + + static godot_variant FromCallable(in Callable @callable) => + VariantUtils.CreateFromCallable(@callable); + + static godot_variant FromSignalInfo(in SignalInfo @signalInfo) => + VariantUtils.CreateFromSignalInfo(@signalInfo); + + static godot_variant FromString(in string @string) => + VariantUtils.CreateFromString(@string); + + static godot_variant FromByteArray(in byte[] byteArray) => + VariantUtils.CreateFromPackedByteArray(byteArray); + + static godot_variant FromInt32Array(in int[] int32Array) => + VariantUtils.CreateFromPackedInt32Array(int32Array); + + static godot_variant FromInt64Array(in long[] int64Array) => + VariantUtils.CreateFromPackedInt64Array(int64Array); + + static godot_variant FromFloatArray(in float[] floatArray) => + VariantUtils.CreateFromPackedFloat32Array(floatArray); + + static godot_variant FromDoubleArray(in double[] doubleArray) => + VariantUtils.CreateFromPackedFloat64Array(doubleArray); + + static godot_variant FromStringArray(in string[] stringArray) => + VariantUtils.CreateFromPackedStringArray(stringArray); + + static godot_variant FromVector2Array(in Vector2[] vector2Array) => + VariantUtils.CreateFromPackedVector2Array(vector2Array); + + static godot_variant FromVector3Array(in Vector3[] vector3Array) => + VariantUtils.CreateFromPackedVector3Array(vector3Array); + + static godot_variant FromColorArray(in Color[] colorArray) => + VariantUtils.CreateFromPackedColorArray(colorArray); + + static godot_variant FromStringNameArray(in StringName[] stringNameArray) => + VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); + + static godot_variant FromNodePathArray(in NodePath[] nodePathArray) => + VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); + + static godot_variant FromRidArray(in RID[] ridArray) => + VariantUtils.CreateFromSystemArrayOfRID(ridArray); + + static godot_variant FromGodotObject(in Godot.Object godotObject) => + VariantUtils.CreateFromGodotObject(godotObject); + + static godot_variant FromStringName(in StringName stringName) => + VariantUtils.CreateFromStringName(stringName); + + static godot_variant FromNodePath(in NodePath nodePath) => + VariantUtils.CreateFromNodePath(nodePath); + + static godot_variant FromRid(in RID rid) => + VariantUtils.CreateFromRID(rid); + + static godot_variant FromGodotDictionary(in Collections.Dictionary godotDictionary) => + VariantUtils.CreateFromDictionary(godotDictionary); + + static godot_variant FromGodotArray(in Collections.Array godotArray) => + VariantUtils.CreateFromArray(godotArray); + + static godot_variant FromVariant(in Variant variant) => + NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); + + var typeOfT = typeof(T); + + if (typeOfT == typeof(bool)) + { + return (delegate* <in T, godot_variant>)(delegate* <in bool, godot_variant>) + &FromBool; + } + + if (typeOfT == typeof(char)) + { + return (delegate* <in T, godot_variant>)(delegate* <in char, godot_variant>) + &FromChar; + } + + if (typeOfT == typeof(sbyte)) + { + return (delegate* <in T, godot_variant>)(delegate* <in sbyte, godot_variant>) + &FromInt8; + } + + if (typeOfT == typeof(short)) + { + return (delegate* <in T, godot_variant>)(delegate* <in short, godot_variant>) + &FromInt16; + } + + if (typeOfT == typeof(int)) + { + return (delegate* <in T, godot_variant>)(delegate* <in int, godot_variant>) + &FromInt32; + } + + if (typeOfT == typeof(long)) + { + return (delegate* <in T, godot_variant>)(delegate* <in long, godot_variant>) + &FromInt64; + } + + if (typeOfT == typeof(byte)) + { + return (delegate* <in T, godot_variant>)(delegate* <in byte, godot_variant>) + &FromUInt8; + } + + if (typeOfT == typeof(ushort)) + { + return (delegate* <in T, godot_variant>)(delegate* <in ushort, godot_variant>) + &FromUInt16; + } + + if (typeOfT == typeof(uint)) + { + return (delegate* <in T, godot_variant>)(delegate* <in uint, godot_variant>) + &FromUInt32; + } + + if (typeOfT == typeof(ulong)) + { + return (delegate* <in T, godot_variant>)(delegate* <in ulong, godot_variant>) + &FromUInt64; + } + + if (typeOfT == typeof(float)) + { + return (delegate* <in T, godot_variant>)(delegate* <in float, godot_variant>) + &FromFloat; + } + + if (typeOfT == typeof(double)) + { + return (delegate* <in T, godot_variant>)(delegate* <in double, godot_variant>) + &FromDouble; + } + + if (typeOfT == typeof(Vector2)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector2, godot_variant>) + &FromVector2; + } + + if (typeOfT == typeof(Vector2i)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector2i, godot_variant>) + &FromVector2I; + } + + if (typeOfT == typeof(Rect2)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Rect2, godot_variant>) + &FromRect2; + } + + if (typeOfT == typeof(Rect2i)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Rect2i, godot_variant>) + &FromRect2I; + } + + if (typeOfT == typeof(Transform2D)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Transform2D, godot_variant>) + &FromTransform2D; + } + + if (typeOfT == typeof(Vector3)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector3, godot_variant>) + &FromVector3; + } + + if (typeOfT == typeof(Vector3i)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector3i, godot_variant>) + &FromVector3I; + } + + if (typeOfT == typeof(Basis)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Basis, godot_variant>) + &FromBasis; + } + + if (typeOfT == typeof(Quaternion)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Quaternion, godot_variant>) + &FromQuaternion; + } + + if (typeOfT == typeof(Transform3D)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Transform3D, godot_variant>) + &FromTransform3D; + } + + if (typeOfT == typeof(AABB)) + { + return (delegate* <in T, godot_variant>)(delegate* <in AABB, godot_variant>) + &FromAabb; + } + + if (typeOfT == typeof(Color)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Color, godot_variant>) + &FromColor; + } + + if (typeOfT == typeof(Plane)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Plane, godot_variant>) + &FromPlane; + } + + if (typeOfT == typeof(Callable)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Callable, godot_variant>) + &FromCallable; + } + + if (typeOfT == typeof(SignalInfo)) + { + return (delegate* <in T, godot_variant>)(delegate* <in SignalInfo, godot_variant>) + &FromSignalInfo; + } + + if (typeOfT.IsEnum) + { + var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); + + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + { + return (delegate* <in T, godot_variant>)(delegate* <in sbyte, godot_variant>) + &FromInt8; + } + case TypeCode.Int16: + { + return (delegate* <in T, godot_variant>)(delegate* <in short, godot_variant>) + &FromInt16; + } + case TypeCode.Int32: + { + return (delegate* <in T, godot_variant>)(delegate* <in int, godot_variant>) + &FromInt32; + } + case TypeCode.Int64: + { + return (delegate* <in T, godot_variant>)(delegate* <in long, godot_variant>) + &FromInt64; + } + case TypeCode.Byte: + { + return (delegate* <in T, godot_variant>)(delegate* <in byte, godot_variant>) + &FromUInt8; + } + case TypeCode.UInt16: + { + return (delegate* <in T, godot_variant>)(delegate* <in ushort, godot_variant>) + &FromUInt16; + } + case TypeCode.UInt32: + { + return (delegate* <in T, godot_variant>)(delegate* <in uint, godot_variant>) + &FromUInt32; + } + case TypeCode.UInt64: + { + return (delegate* <in T, godot_variant>)(delegate* <in ulong, godot_variant>) + &FromUInt64; + } + default: + return null; + } + } + + if (typeOfT == typeof(string)) + { + return (delegate* <in T, godot_variant>)(delegate* <in string, godot_variant>) + &FromString; + } + + if (typeOfT == typeof(byte[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in byte[], godot_variant>) + &FromByteArray; + } + + if (typeOfT == typeof(int[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in int[], godot_variant>) + &FromInt32Array; + } + + if (typeOfT == typeof(long[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in long[], godot_variant>) + &FromInt64Array; + } + + if (typeOfT == typeof(float[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in float[], godot_variant>) + &FromFloatArray; + } + + if (typeOfT == typeof(double[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in double[], godot_variant>) + &FromDoubleArray; + } + + if (typeOfT == typeof(string[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in string[], godot_variant>) + &FromStringArray; + } + + if (typeOfT == typeof(Vector2[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector2[], godot_variant>) + &FromVector2Array; + } + + if (typeOfT == typeof(Vector3[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector3[], godot_variant>) + &FromVector3Array; + } + + if (typeOfT == typeof(Color[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in Color[], godot_variant>) + &FromColorArray; + } + + if (typeOfT == typeof(StringName[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in StringName[], godot_variant>) + &FromStringNameArray; + } + + if (typeOfT == typeof(NodePath[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in NodePath[], godot_variant>) + &FromNodePathArray; + } + + if (typeOfT == typeof(RID[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in RID[], godot_variant>) + &FromRidArray; + } + + if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Godot.Object, godot_variant>) + &FromGodotObject; + } + + if (typeOfT == typeof(StringName)) + { + return (delegate* <in T, godot_variant>)(delegate* <in StringName, godot_variant>) + &FromStringName; + } + + if (typeOfT == typeof(NodePath)) + { + return (delegate* <in T, godot_variant>)(delegate* <in NodePath, godot_variant>) + &FromNodePath; + } + + if (typeOfT == typeof(RID)) + { + return (delegate* <in T, godot_variant>)(delegate* <in RID, godot_variant>) + &FromRid; + } + + if (typeOfT == typeof(Godot.Collections.Dictionary)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Godot.Collections.Dictionary, godot_variant>) + &FromGodotDictionary; + } + + if (typeOfT == typeof(Godot.Collections.Array)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Godot.Collections.Array, godot_variant>) + &FromGodotArray; + } + + if (typeOfT == typeof(Variant)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Variant, godot_variant>) + &FromVariant; + } + + return null; + } + + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + internal static delegate* <in godot_variant, T> GetToManagedCallback<T>() + { + static bool ToBool(in godot_variant variant) => + VariantUtils.ConvertToBool(variant); + + static char ToChar(in godot_variant variant) => + VariantUtils.ConvertToChar(variant); + + static sbyte ToInt8(in godot_variant variant) => + VariantUtils.ConvertToInt8(variant); + + static short ToInt16(in godot_variant variant) => + VariantUtils.ConvertToInt16(variant); + + static int ToInt32(in godot_variant variant) => + VariantUtils.ConvertToInt32(variant); + + static long ToInt64(in godot_variant variant) => + VariantUtils.ConvertToInt64(variant); + + static byte ToUInt8(in godot_variant variant) => + VariantUtils.ConvertToUInt8(variant); + + static ushort ToUInt16(in godot_variant variant) => + VariantUtils.ConvertToUInt16(variant); + + static uint ToUInt32(in godot_variant variant) => + VariantUtils.ConvertToUInt32(variant); + + static ulong ToUInt64(in godot_variant variant) => + VariantUtils.ConvertToUInt64(variant); + + static float ToFloat(in godot_variant variant) => + VariantUtils.ConvertToFloat32(variant); + + static double ToDouble(in godot_variant variant) => + VariantUtils.ConvertToFloat64(variant); + + static Vector2 ToVector2(in godot_variant variant) => + VariantUtils.ConvertToVector2(variant); + + static Vector2i ToVector2I(in godot_variant variant) => + VariantUtils.ConvertToVector2i(variant); + + static Rect2 ToRect2(in godot_variant variant) => + VariantUtils.ConvertToRect2(variant); + + static Rect2i ToRect2I(in godot_variant variant) => + VariantUtils.ConvertToRect2i(variant); + + static Transform2D ToTransform2D(in godot_variant variant) => + VariantUtils.ConvertToTransform2D(variant); + + static Vector3 ToVector3(in godot_variant variant) => + VariantUtils.ConvertToVector3(variant); + + static Vector3i ToVector3I(in godot_variant variant) => + VariantUtils.ConvertToVector3i(variant); + + static Basis ToBasis(in godot_variant variant) => + VariantUtils.ConvertToBasis(variant); + + static Quaternion ToQuaternion(in godot_variant variant) => + VariantUtils.ConvertToQuaternion(variant); + + static Transform3D ToTransform3D(in godot_variant variant) => + VariantUtils.ConvertToTransform3D(variant); + + static AABB ToAabb(in godot_variant variant) => + VariantUtils.ConvertToAABB(variant); + + static Color ToColor(in godot_variant variant) => + VariantUtils.ConvertToColor(variant); + + static Plane ToPlane(in godot_variant variant) => + VariantUtils.ConvertToPlane(variant); + + static Callable ToCallable(in godot_variant variant) => + VariantUtils.ConvertToCallableManaged(variant); + + static SignalInfo ToSignalInfo(in godot_variant variant) => + VariantUtils.ConvertToSignalInfo(variant); + + static string ToString(in godot_variant variant) => + VariantUtils.ConvertToStringObject(variant); + + static byte[] ToByteArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedByteArrayToSystemArray(variant); + + static int[] ToInt32Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(variant); + + static long[] ToInt64Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(variant); + + static float[] ToFloatArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(variant); + + static double[] ToDoubleArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(variant); + + static string[] ToStringArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedStringArrayToSystemArray(variant); + + static Vector2[] ToVector2Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(variant); + + static Vector3[] ToVector3Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(variant); + + static Color[] ToColorArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedColorArrayToSystemArray(variant); + + static StringName[] ToStringNameArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfStringName(variant); + + static NodePath[] ToNodePathArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfNodePath(variant); + + static RID[] ToRidArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfRID(variant); + + static Godot.Object ToGodotObject(in godot_variant variant) => + VariantUtils.ConvertToGodotObject(variant); + + static StringName ToStringName(in godot_variant variant) => + VariantUtils.ConvertToStringNameObject(variant); + + static NodePath ToNodePath(in godot_variant variant) => + VariantUtils.ConvertToNodePathObject(variant); + + static RID ToRid(in godot_variant variant) => + VariantUtils.ConvertToRID(variant); + + static Collections.Dictionary ToGodotDictionary(in godot_variant variant) => + VariantUtils.ConvertToDictionaryObject(variant); + + static Collections.Array ToGodotArray(in godot_variant variant) => + VariantUtils.ConvertToArrayObject(variant); + + static Variant ToVariant(in godot_variant variant) => + Variant.CreateCopyingBorrowed(variant); + + var typeOfT = typeof(T); + + // ReSharper disable RedundantCast + // Rider is being stupid here. These casts are definitely needed. We get build errors without them. + + if (typeOfT == typeof(bool)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, bool>) + &ToBool; + } + + if (typeOfT == typeof(char)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, char>) + &ToChar; + } + + if (typeOfT == typeof(sbyte)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, sbyte>) + &ToInt8; + } + + if (typeOfT == typeof(short)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, short>) + &ToInt16; + } + + if (typeOfT == typeof(int)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, int>) + &ToInt32; + } + + if (typeOfT == typeof(long)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, long>) + &ToInt64; + } + + if (typeOfT == typeof(byte)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, byte>) + &ToUInt8; + } + + if (typeOfT == typeof(ushort)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ushort>) + &ToUInt16; + } + + if (typeOfT == typeof(uint)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, uint>) + &ToUInt32; + } + + if (typeOfT == typeof(ulong)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ulong>) + &ToUInt64; + } + + if (typeOfT == typeof(float)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, float>) + &ToFloat; + } + + if (typeOfT == typeof(double)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, double>) + &ToDouble; + } + + if (typeOfT == typeof(Vector2)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector2>) + &ToVector2; + } + + if (typeOfT == typeof(Vector2i)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector2i>) + &ToVector2I; + } + + if (typeOfT == typeof(Rect2)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Rect2>) + &ToRect2; + } + + if (typeOfT == typeof(Rect2i)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Rect2i>) + &ToRect2I; + } + + if (typeOfT == typeof(Transform2D)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Transform2D>) + &ToTransform2D; + } + + if (typeOfT == typeof(Vector3)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector3>) + &ToVector3; + } + + if (typeOfT == typeof(Vector3i)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector3i>) + &ToVector3I; + } + + if (typeOfT == typeof(Basis)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Basis>) + &ToBasis; + } + + if (typeOfT == typeof(Quaternion)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Quaternion>) + &ToQuaternion; + } + + if (typeOfT == typeof(Transform3D)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Transform3D>) + &ToTransform3D; + } + + if (typeOfT == typeof(AABB)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, AABB>) + &ToAabb; + } + + if (typeOfT == typeof(Color)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Color>) + &ToColor; + } + + if (typeOfT == typeof(Plane)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Plane>) + &ToPlane; + } + + if (typeOfT == typeof(Callable)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Callable>) + &ToCallable; + } + + if (typeOfT == typeof(SignalInfo)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, SignalInfo>) + &ToSignalInfo; + } + + if (typeOfT.IsEnum) + { + var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); + + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, sbyte>) + &ToInt8; + } + case TypeCode.Int16: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, short>) + &ToInt16; + } + case TypeCode.Int32: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, int>) + &ToInt32; + } + case TypeCode.Int64: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, long>) + &ToInt64; + } + case TypeCode.Byte: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, byte>) + &ToUInt8; + } + case TypeCode.UInt16: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ushort>) + &ToUInt16; + } + case TypeCode.UInt32: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, uint>) + &ToUInt32; + } + case TypeCode.UInt64: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ulong>) + &ToUInt64; + } + default: + return null; + } + } + + if (typeOfT == typeof(string)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, string>) + &ToString; + } + + if (typeOfT == typeof(byte[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, byte[]>) + &ToByteArray; + } + + if (typeOfT == typeof(int[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, int[]>) + &ToInt32Array; + } + + if (typeOfT == typeof(long[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, long[]>) + &ToInt64Array; + } + + if (typeOfT == typeof(float[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, float[]>) + &ToFloatArray; + } + + if (typeOfT == typeof(double[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, double[]>) + &ToDoubleArray; + } + + if (typeOfT == typeof(string[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, string[]>) + &ToStringArray; + } + + if (typeOfT == typeof(Vector2[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector2[]>) + &ToVector2Array; + } + + if (typeOfT == typeof(Vector3[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector3[]>) + &ToVector3Array; + } + + if (typeOfT == typeof(Color[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Color[]>) + &ToColorArray; + } + + if (typeOfT == typeof(StringName[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, StringName[]>) + &ToStringNameArray; + } + + if (typeOfT == typeof(NodePath[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, NodePath[]>) + &ToNodePathArray; + } + + if (typeOfT == typeof(RID[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, RID[]>) + &ToRidArray; + } + + if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Godot.Object>) + &ToGodotObject; + } + + if (typeOfT == typeof(StringName)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, StringName>) + &ToStringName; + } + + if (typeOfT == typeof(NodePath)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, NodePath>) + &ToNodePath; + } + + if (typeOfT == typeof(RID)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, RID>) + &ToRid; + } + + if (typeOfT == typeof(Godot.Collections.Dictionary)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Godot.Collections.Dictionary>) + &ToGodotDictionary; + } + + if (typeOfT == typeof(Godot.Collections.Array)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Godot.Collections.Array>) + &ToGodotArray; + } + + if (typeOfT == typeof(Variant)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Variant>) + &ToVariant; + } + + // ReSharper restore RedundantCast + + return null; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs new file mode 100644 index 0000000000..46f31bbf4e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs @@ -0,0 +1,33 @@ +using System; + +namespace Godot.NativeInterop +{ + internal readonly ref struct VariantSpanDisposer + { + private readonly Span<godot_variant.movable> _variantSpan; + + // IMPORTANT: The span element must be default initialized. + // Make sure call Clear() on the span if it was created with stackalloc. + public VariantSpanDisposer(Span<godot_variant.movable> variantSpan) + { + _variantSpan = variantSpan; + } + + public void Dispose() + { + for (int i = 0; i < _variantSpan.Length; i++) + _variantSpan[i].DangerousSelfRef.Dispose(); + } + } + + internal static class VariantSpanExtensions + { + // Used to make sure we always initialize the span values to the default, + // as we need that in order to safely dispose all elements after. + public static Span<godot_variant.movable> Cleared(this Span<godot_variant.movable> span) + { + span.Clear(); + return span; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs new file mode 100644 index 0000000000..491ccf904e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -0,0 +1,611 @@ +using System; +using System.Runtime.CompilerServices; +using Godot.Collections; + +// ReSharper disable InconsistentNaming + +#nullable enable + +namespace Godot.NativeInterop +{ + public static class VariantUtils + { + public static godot_variant CreateFromRID(RID from) + => new() { Type = Variant.Type.Rid, RID = from }; + + public static godot_variant CreateFromBool(bool from) + => new() { Type = Variant.Type.Bool, Bool = from.ToGodotBool() }; + + public static godot_variant CreateFromInt(long from) + => new() { Type = Variant.Type.Int, Int = from }; + + public static godot_variant CreateFromInt(ulong from) + => new() { Type = Variant.Type.Int, Int = (long)from }; + + public static godot_variant CreateFromFloat(double from) + => new() { Type = Variant.Type.Float, Float = from }; + + public static godot_variant CreateFromVector2(Vector2 from) + => new() { Type = Variant.Type.Vector2, Vector2 = from }; + + public static godot_variant CreateFromVector2i(Vector2i from) + => new() { Type = Variant.Type.Vector2i, Vector2i = from }; + + public static godot_variant CreateFromVector3(Vector3 from) + => new() { Type = Variant.Type.Vector3, Vector3 = from }; + + public static godot_variant CreateFromVector3i(Vector3i from) + => new() { Type = Variant.Type.Vector3i, Vector3i = from }; + + public static godot_variant CreateFromRect2(Rect2 from) + => new() { Type = Variant.Type.Rect2, Rect2 = from }; + + public static godot_variant CreateFromRect2i(Rect2i from) + => new() { Type = Variant.Type.Rect2i, Rect2i = from }; + + public static godot_variant CreateFromQuaternion(Quaternion from) + => new() { Type = Variant.Type.Quaternion, Quaternion = from }; + + public static godot_variant CreateFromColor(Color from) + => new() { Type = Variant.Type.Color, Color = from }; + + public static godot_variant CreateFromPlane(Plane from) + => new() { Type = Variant.Type.Plane, Plane = from }; + + public static godot_variant CreateFromTransform2D(Transform2D from) + { + NativeFuncs.godotsharp_variant_new_transform2d(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromVector4(Vector4 from) + { + NativeFuncs.godotsharp_variant_new_vector4(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromVector4i(Vector4i from) + { + NativeFuncs.godotsharp_variant_new_vector4i(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromBasis(Basis from) + { + NativeFuncs.godotsharp_variant_new_basis(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromTransform3D(Transform3D from) + { + NativeFuncs.godotsharp_variant_new_transform3d(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromProjection(Projection from) + { + NativeFuncs.godotsharp_variant_new_projection(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromAABB(AABB from) + { + NativeFuncs.godotsharp_variant_new_aabb(out godot_variant ret, from); + return ret; + } + + // Explicit name to make it very clear + public static godot_variant CreateFromCallableTakingOwnershipOfDisposableValue(godot_callable from) + => new() { Type = Variant.Type.Callable, Callable = from }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromCallable(Callable from) + => CreateFromCallableTakingOwnershipOfDisposableValue( + Marshaling.ConvertCallableToNative(from)); + + // Explicit name to make it very clear + public static godot_variant CreateFromSignalTakingOwnershipOfDisposableValue(godot_signal from) + => new() { Type = Variant.Type.Signal, Signal = from }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromSignalInfo(SignalInfo from) + => CreateFromSignalTakingOwnershipOfDisposableValue( + Marshaling.ConvertSignalToNative(from)); + + // Explicit name to make it very clear + public static godot_variant CreateFromStringTakingOwnershipOfDisposableValue(godot_string from) + => new() { Type = Variant.Type.String, String = from }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromString(string? from) + => CreateFromStringTakingOwnershipOfDisposableValue(Marshaling.ConvertStringToNative(from)); + + public static godot_variant CreateFromPackedByteArray(in godot_packed_byte_array from) + { + NativeFuncs.godotsharp_variant_new_packed_byte_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedInt32Array(in godot_packed_int32_array from) + { + NativeFuncs.godotsharp_variant_new_packed_int32_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedInt64Array(in godot_packed_int64_array from) + { + NativeFuncs.godotsharp_variant_new_packed_int64_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedFloat32Array(in godot_packed_float32_array from) + { + NativeFuncs.godotsharp_variant_new_packed_float32_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedFloat64Array(in godot_packed_float64_array from) + { + NativeFuncs.godotsharp_variant_new_packed_float64_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedStringArray(in godot_packed_string_array from) + { + NativeFuncs.godotsharp_variant_new_packed_string_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedVector2Array(in godot_packed_vector2_array from) + { + NativeFuncs.godotsharp_variant_new_packed_vector2_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedVector3Array(in godot_packed_vector3_array from) + { + NativeFuncs.godotsharp_variant_new_packed_vector3_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedColorArray(in godot_packed_color_array from) + { + NativeFuncs.godotsharp_variant_new_packed_color_array(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedByteArray(Span<byte> from) + => CreateFromPackedByteArray(Marshaling.ConvertSystemArrayToNativePackedByteArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedInt32Array(Span<int> from) + => CreateFromPackedInt32Array(Marshaling.ConvertSystemArrayToNativePackedInt32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedInt64Array(Span<long> from) + => CreateFromPackedInt64Array(Marshaling.ConvertSystemArrayToNativePackedInt64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedFloat32Array(Span<float> from) + => CreateFromPackedFloat32Array(Marshaling.ConvertSystemArrayToNativePackedFloat32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedFloat64Array(Span<double> from) + => CreateFromPackedFloat64Array(Marshaling.ConvertSystemArrayToNativePackedFloat64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedStringArray(Span<string> from) + => CreateFromPackedStringArray(Marshaling.ConvertSystemArrayToNativePackedStringArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedVector2Array(Span<Vector2> from) + => CreateFromPackedVector2Array(Marshaling.ConvertSystemArrayToNativePackedVector2Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedVector3Array(Span<Vector3> from) + => CreateFromPackedVector3Array(Marshaling.ConvertSystemArrayToNativePackedVector3Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedColorArray(Span<Color> from) + => CreateFromPackedColorArray(Marshaling.ConvertSystemArrayToNativePackedColorArray(from)); + + public static godot_variant CreateFromSystemArrayOfStringName(Span<StringName> from) + => CreateFromArray(new Collections.Array(from)); + + public static godot_variant CreateFromSystemArrayOfNodePath(Span<NodePath> from) + => CreateFromArray(new Collections.Array(from)); + + public static godot_variant CreateFromSystemArrayOfRID(Span<RID> from) + => CreateFromArray(new Collections.Array(from)); + + // ReSharper disable once RedundantNameQualifier + public static godot_variant CreateFromSystemArrayOfGodotObject(Godot.Object[]? from) + { + if (from == null) + return default; // Nil + using var fromGodot = new Collections.Array(from); + return CreateFromArray((godot_array)fromGodot.NativeValue); + } + + public static godot_variant CreateFromArray(godot_array from) + { + NativeFuncs.godotsharp_variant_new_array(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromArray(Collections.Array? from) + => from != null ? CreateFromArray((godot_array)from.NativeValue) : default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromArray<T>(Array<T>? from) + => from != null ? CreateFromArray((godot_array)((Collections.Array)from).NativeValue) : default; + + public static godot_variant CreateFromDictionary(godot_dictionary from) + { + NativeFuncs.godotsharp_variant_new_dictionary(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromDictionary(Dictionary? from) + => from != null ? CreateFromDictionary((godot_dictionary)from.NativeValue) : default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromDictionary<TKey, TValue>(Dictionary<TKey, TValue>? from) + => from != null ? CreateFromDictionary((godot_dictionary)((Dictionary)from).NativeValue) : default; + + public static godot_variant CreateFromStringName(godot_string_name from) + { + NativeFuncs.godotsharp_variant_new_string_name(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromStringName(StringName? from) + => from != null ? CreateFromStringName((godot_string_name)from.NativeValue) : default; + + public static godot_variant CreateFromNodePath(godot_node_path from) + { + NativeFuncs.godotsharp_variant_new_node_path(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromNodePath(NodePath? from) + => from != null ? CreateFromNodePath((godot_node_path)from.NativeValue) : default; + + public static godot_variant CreateFromGodotObjectPtr(IntPtr from) + { + if (from == IntPtr.Zero) + return new godot_variant(); + NativeFuncs.godotsharp_variant_new_object(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantNameQualifier + public static godot_variant CreateFromGodotObject(Godot.Object? from) + => from != null ? CreateFromGodotObjectPtr(Object.GetPtr(from)) : default; + + // We avoid the internal call if the stored type is the same we want. + + public static bool ConvertToBool(in godot_variant p_var) + => p_var.Type == Variant.Type.Bool ? + p_var.Bool.ToBool() : + NativeFuncs.godotsharp_variant_as_bool(p_var).ToBool(); + + public static char ConvertToChar(in godot_variant p_var) + => (char)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static sbyte ConvertToInt8(in godot_variant p_var) + => (sbyte)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static short ConvertToInt16(in godot_variant p_var) + => (short)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static int ConvertToInt32(in godot_variant p_var) + => (int)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static long ConvertToInt64(in godot_variant p_var) + => p_var.Type == Variant.Type.Int ? p_var.Int : NativeFuncs.godotsharp_variant_as_int(p_var); + + public static byte ConvertToUInt8(in godot_variant p_var) + => (byte)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static ushort ConvertToUInt16(in godot_variant p_var) + => (ushort)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static uint ConvertToUInt32(in godot_variant p_var) + => (uint)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static ulong ConvertToUInt64(in godot_variant p_var) + => (ulong)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static float ConvertToFloat32(in godot_variant p_var) + => (float)(p_var.Type == Variant.Type.Float ? + p_var.Float : + NativeFuncs.godotsharp_variant_as_float(p_var)); + + public static double ConvertToFloat64(in godot_variant p_var) + => p_var.Type == Variant.Type.Float ? + p_var.Float : + NativeFuncs.godotsharp_variant_as_float(p_var); + + public static Vector2 ConvertToVector2(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector2 ? + p_var.Vector2 : + NativeFuncs.godotsharp_variant_as_vector2(p_var); + + public static Vector2i ConvertToVector2i(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector2i ? + p_var.Vector2i : + NativeFuncs.godotsharp_variant_as_vector2i(p_var); + + public static Rect2 ConvertToRect2(in godot_variant p_var) + => p_var.Type == Variant.Type.Rect2 ? + p_var.Rect2 : + NativeFuncs.godotsharp_variant_as_rect2(p_var); + + public static Rect2i ConvertToRect2i(in godot_variant p_var) + => p_var.Type == Variant.Type.Rect2i ? + p_var.Rect2i : + NativeFuncs.godotsharp_variant_as_rect2i(p_var); + + public static unsafe Transform2D ConvertToTransform2D(in godot_variant p_var) + => p_var.Type == Variant.Type.Transform2d ? + *p_var.Transform2D : + NativeFuncs.godotsharp_variant_as_transform2d(p_var); + + public static Vector3 ConvertToVector3(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector3 ? + p_var.Vector3 : + NativeFuncs.godotsharp_variant_as_vector3(p_var); + + public static Vector3i ConvertToVector3i(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector3i ? + p_var.Vector3i : + NativeFuncs.godotsharp_variant_as_vector3i(p_var); + + public static unsafe Vector4 ConvertToVector4(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector4 ? + *p_var.Vector4 : + NativeFuncs.godotsharp_variant_as_vector4(p_var); + + public static unsafe Vector4i ConvertToVector4i(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector4i ? + *p_var.Vector4i : + NativeFuncs.godotsharp_variant_as_vector4i(p_var); + + public static unsafe Basis ConvertToBasis(in godot_variant p_var) + => p_var.Type == Variant.Type.Basis ? + *p_var.Basis : + NativeFuncs.godotsharp_variant_as_basis(p_var); + + public static Quaternion ConvertToQuaternion(in godot_variant p_var) + => p_var.Type == Variant.Type.Quaternion ? + p_var.Quaternion : + NativeFuncs.godotsharp_variant_as_quaternion(p_var); + + public static unsafe Transform3D ConvertToTransform3D(in godot_variant p_var) + => p_var.Type == Variant.Type.Transform3d ? + *p_var.Transform3D : + NativeFuncs.godotsharp_variant_as_transform3d(p_var); + + public static unsafe Projection ConvertToProjection(in godot_variant p_var) + => p_var.Type == Variant.Type.Projection ? + *p_var.Projection : + NativeFuncs.godotsharp_variant_as_projection(p_var); + + public static unsafe AABB ConvertToAABB(in godot_variant p_var) + => p_var.Type == Variant.Type.Aabb ? + *p_var.AABB : + NativeFuncs.godotsharp_variant_as_aabb(p_var); + + public static Color ConvertToColor(in godot_variant p_var) + => p_var.Type == Variant.Type.Color ? + p_var.Color : + NativeFuncs.godotsharp_variant_as_color(p_var); + + public static Plane ConvertToPlane(in godot_variant p_var) + => p_var.Type == Variant.Type.Plane ? + p_var.Plane : + NativeFuncs.godotsharp_variant_as_plane(p_var); + + public static RID ConvertToRID(in godot_variant p_var) + => p_var.Type == Variant.Type.Rid ? + p_var.RID : + NativeFuncs.godotsharp_variant_as_rid(p_var); + + public static IntPtr ConvertToGodotObjectPtr(in godot_variant p_var) + => p_var.Type == Variant.Type.Object ? p_var.Object : IntPtr.Zero; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantNameQualifier + public static Godot.Object ConvertToGodotObject(in godot_variant p_var) + => InteropUtils.UnmanagedGetManaged(ConvertToGodotObjectPtr(p_var)); + + public static string ConvertToStringObject(in godot_variant p_var) + { + switch (p_var.Type) + { + case Variant.Type.Nil: + return ""; // Otherwise, Variant -> String would return the string "Null" + case Variant.Type.String: + { + // We avoid the internal call if the stored type is the same we want. + return Marshaling.ConvertStringToManaged(p_var.String); + } + default: + { + using godot_string godotString = NativeFuncs.godotsharp_variant_as_string(p_var); + return Marshaling.ConvertStringToManaged(godotString); + } + } + } + + public static godot_string_name ConvertToStringName(in godot_variant p_var) + => p_var.Type == Variant.Type.StringName ? + NativeFuncs.godotsharp_string_name_new_copy(p_var.StringName) : + NativeFuncs.godotsharp_variant_as_string_name(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static StringName ConvertToStringNameObject(in godot_variant p_var) + => StringName.CreateTakingOwnershipOfDisposableValue(ConvertToStringName(p_var)); + + public static godot_node_path ConvertToNodePath(in godot_variant p_var) + => p_var.Type == Variant.Type.NodePath ? + NativeFuncs.godotsharp_node_path_new_copy(p_var.NodePath) : + NativeFuncs.godotsharp_variant_as_node_path(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NodePath ConvertToNodePathObject(in godot_variant p_var) + => NodePath.CreateTakingOwnershipOfDisposableValue(ConvertToNodePath(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_callable ConvertToCallable(in godot_variant p_var) + => NativeFuncs.godotsharp_variant_as_callable(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Callable ConvertToCallableManaged(in godot_variant p_var) + => Marshaling.ConvertCallableToManaged(ConvertToCallable(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_signal ConvertToSignal(in godot_variant p_var) + => NativeFuncs.godotsharp_variant_as_signal(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SignalInfo ConvertToSignalInfo(in godot_variant p_var) + => Marshaling.ConvertSignalToManaged(ConvertToSignal(p_var)); + + public static godot_array ConvertToArray(in godot_variant p_var) + => p_var.Type == Variant.Type.Array ? + NativeFuncs.godotsharp_array_new_copy(p_var.Array) : + NativeFuncs.godotsharp_variant_as_array(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Collections.Array ConvertToArrayObject(in godot_variant p_var) + => Collections.Array.CreateTakingOwnershipOfDisposableValue(ConvertToArray(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Array<T> ConvertToArrayObject<T>(in godot_variant p_var) + => Array<T>.CreateTakingOwnershipOfDisposableValue(ConvertToArray(p_var)); + + public static godot_dictionary ConvertToDictionary(in godot_variant p_var) + => p_var.Type == Variant.Type.Dictionary ? + NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary) : + NativeFuncs.godotsharp_variant_as_dictionary(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary ConvertToDictionaryObject(in godot_variant p_var) + => Dictionary.CreateTakingOwnershipOfDisposableValue(ConvertToDictionary(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary<TKey, TValue> ConvertToDictionaryObject<TKey, TValue>(in godot_variant p_var) + => Dictionary<TKey, TValue>.CreateTakingOwnershipOfDisposableValue(ConvertToDictionary(p_var)); + + public static byte[] ConvertAsPackedByteArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_byte_array(p_var); + return Marshaling.ConvertNativePackedByteArrayToSystemArray(packedArray); + } + + public static int[] ConvertAsPackedInt32ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_int32_array(p_var); + return Marshaling.ConvertNativePackedInt32ArrayToSystemArray(packedArray); + } + + public static long[] ConvertAsPackedInt64ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_int64_array(p_var); + return Marshaling.ConvertNativePackedInt64ArrayToSystemArray(packedArray); + } + + public static float[] ConvertAsPackedFloat32ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_float32_array(p_var); + return Marshaling.ConvertNativePackedFloat32ArrayToSystemArray(packedArray); + } + + public static double[] ConvertAsPackedFloat64ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_float64_array(p_var); + return Marshaling.ConvertNativePackedFloat64ArrayToSystemArray(packedArray); + } + + public static string[] ConvertAsPackedStringArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_string_array(p_var); + return Marshaling.ConvertNativePackedStringArrayToSystemArray(packedArray); + } + + public static Vector2[] ConvertAsPackedVector2ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_vector2_array(p_var); + return Marshaling.ConvertNativePackedVector2ArrayToSystemArray(packedArray); + } + + public static Vector3[] ConvertAsPackedVector3ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_vector3_array(p_var); + return Marshaling.ConvertNativePackedVector3ArrayToSystemArray(packedArray); + } + + public static Color[] ConvertAsPackedColorArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_color_array(p_var); + return Marshaling.ConvertNativePackedColorArrayToSystemArray(packedArray); + } + + public static StringName[] ConvertToSystemArrayOfStringName(in godot_variant p_var) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfStringName(godotArray); + } + + public static NodePath[] ConvertToSystemArrayOfNodePath(in godot_variant p_var) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfNodePath(godotArray); + } + + public static RID[] ConvertToSystemArrayOfRID(in godot_variant p_var) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfRID(godotArray); + } + + public static T[] ConvertToSystemArrayOfGodotObject<T>(in godot_variant p_var) + // ReSharper disable once RedundantNameQualifier + where T : Godot.Object + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfGodotObjectType<T>(godotArray); + } + + // ReSharper disable once RedundantNameQualifier + public static Godot.Object[] ConvertToSystemArrayOfGodotObject(in godot_variant p_var, Type type) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfGodotObjectType(godotArray, type); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs index 9ae01016cb..b02bd167a1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -39,22 +39,11 @@ namespace Godot /// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene. /// </code> /// </example> - public sealed partial class NodePath : IDisposable + public sealed class NodePath : IDisposable { - private bool _disposed = false; + internal godot_node_path.movable NativeValue; - private IntPtr ptr; - - internal static IntPtr GetPtr(NodePath instance) - { - if (instance == null) - throw new NullReferenceException($"The instance of type {nameof(NodePath)} is null."); - - if (instance._disposed) - throw new ObjectDisposedException(instance.GetType().FullName); - - return instance.ptr; - } + private WeakReference<IDisposable> _weakReferenceToSelf; ~NodePath() { @@ -70,29 +59,33 @@ namespace Godot GC.SuppressFinalize(this); } - private void Dispose(bool disposing) + public void Dispose(bool disposing) { - if (_disposed) - return; + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); - if (ptr != IntPtr.Zero) + if (_weakReferenceToSelf != null) { - godot_icall_NodePath_Dtor(ptr); - ptr = IntPtr.Zero; + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); } - - _disposed = true; } - internal NodePath(IntPtr ptr) + private NodePath(godot_node_path nativeValueToOwn) { - this.ptr = ptr; + NativeValue = (godot_node_path.movable)nativeValueToOwn; + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } + // Explicit name to make it very clear + internal static NodePath CreateTakingOwnershipOfDisposableValue(godot_node_path nativeValueToOwn) + => new NodePath(nativeValueToOwn); + /// <summary> /// Constructs an empty <see cref="NodePath"/>. /// </summary> - public NodePath() : this(string.Empty) { } + public NodePath() + { + } /// <summary> /// Constructs a <see cref="NodePath"/> from a string <paramref name="path"/>, @@ -125,7 +118,11 @@ namespace Godot /// <param name="path">A string that represents a path in a scene tree.</param> public NodePath(string path) { - ptr = godot_icall_NodePath_Ctor(path); + if (!string.IsNullOrEmpty(path)) + { + NativeValue = (godot_node_path.movable)NativeFuncs.godotsharp_node_path_new_from_string(path); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } } /// <summary> @@ -138,7 +135,7 @@ namespace Godot /// Converts this <see cref="NodePath"/> to a string. /// </summary> /// <param name="from">The <see cref="NodePath"/> to convert.</param> - public static implicit operator string(NodePath from) => from.ToString(); + public static implicit operator string(NodePath from) => from?.ToString(); /// <summary> /// Converts this <see cref="NodePath"/> to a string. @@ -146,7 +143,13 @@ namespace Godot /// <returns>A string representation of this <see cref="NodePath"/>.</returns> public override string ToString() { - return godot_icall_NodePath_operator_String(GetPtr(this)); + if (IsEmpty) + return string.Empty; + + var src = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_as_string(out godot_string dest, src); + using (dest) + return Marshaling.ConvertStringToManaged(dest); } /// <summary> @@ -166,7 +169,10 @@ namespace Godot /// <returns>The <see cref="NodePath"/> as a pure property path.</returns> public NodePath GetAsPropertyPath() { - return new NodePath(godot_icall_NodePath_get_as_property_path(GetPtr(this))); + godot_node_path propertyPath = default; + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_as_property_path(self, ref propertyPath); + return CreateTakingOwnershipOfDisposableValue(propertyPath); } /// <summary> @@ -181,7 +187,10 @@ namespace Godot /// <returns>The names concatenated with <c>/</c>.</returns> public string GetConcatenatedNames() { - return godot_icall_NodePath_get_concatenated_names(GetPtr(this)); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_concatenated_names(self, out godot_string names); + using (names) + return Marshaling.ConvertStringToManaged(names); } /// <summary> @@ -195,9 +204,12 @@ namespace Godot /// </code> /// </example> /// <returns>The subnames concatenated with <c>:</c>.</returns> - public string GetConcatenatedSubnames() + public string GetConcatenatedSubNames() { - return godot_icall_NodePath_get_concatenated_subnames(GetPtr(this)); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_concatenated_subnames(self, out godot_string subNames); + using (subNames) + return Marshaling.ConvertStringToManaged(subNames); } /// <summary> @@ -215,28 +227,35 @@ namespace Godot /// <returns>The name at the given index <paramref name="idx"/>.</returns> public string GetName(int idx) { - return godot_icall_NodePath_get_name(GetPtr(this), idx); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_name(self, idx, out godot_string name); + using (name) + return Marshaling.ConvertStringToManaged(name); } /// <summary> /// Gets the number of node names which make up the path. - /// Subnames (see <see cref="GetSubnameCount"/>) are not included. + /// Subnames (see <see cref="GetSubNameCount"/>) are not included. /// For example, <c>"Path2D/PathFollow2D/Sprite2D"</c> has 3 names. /// </summary> /// <returns>The number of node names which make up the path.</returns> public int GetNameCount() { - return godot_icall_NodePath_get_name_count(GetPtr(this)); + var self = (godot_node_path)NativeValue; + return NativeFuncs.godotsharp_node_path_get_name_count(self); } /// <summary> - /// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubnameCount"/>). + /// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubNameCount"/>). /// </summary> /// <param name="idx">The subname index.</param> /// <returns>The subname at the given index <paramref name="idx"/>.</returns> - public string GetSubname(int idx) + public string GetSubName(int idx) { - return godot_icall_NodePath_get_subname(GetPtr(this), idx); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_subname(self, idx, out godot_string subName); + using (subName) + return Marshaling.ConvertStringToManaged(subName); } /// <summary> @@ -245,9 +264,10 @@ namespace Godot /// For example, <c>"Path2D/PathFollow2D/Sprite2D:texture:load_path"</c> has 2 subnames. /// </summary> /// <returns>The number of subnames in the path.</returns> - public int GetSubnameCount() + public int GetSubNameCount() { - return godot_icall_NodePath_get_subname_count(GetPtr(this)); + var self = (godot_node_path)NativeValue; + return NativeFuncs.godotsharp_node_path_get_subname_count(self); } /// <summary> @@ -259,52 +279,14 @@ namespace Godot /// <returns>If the <see cref="NodePath"/> is an absolute path.</returns> public bool IsAbsolute() { - return godot_icall_NodePath_is_absolute(GetPtr(this)); + var self = (godot_node_path)NativeValue; + return NativeFuncs.godotsharp_node_path_is_absolute(self).ToBool(); } /// <summary> /// Returns <see langword="true"/> if the node path is empty. /// </summary> /// <returns>If the <see cref="NodePath"/> is empty.</returns> - public bool IsEmpty() - { - return godot_icall_NodePath_is_empty(GetPtr(this)); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr godot_icall_NodePath_Ctor(string path); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void godot_icall_NodePath_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_operator_String(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_concatenated_names(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_name(IntPtr ptr, int arg1); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int godot_icall_NodePath_get_name_count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_subname(IntPtr ptr, int arg1); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int godot_icall_NodePath_get_subname_count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool godot_icall_NodePath_is_absolute(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool godot_icall_NodePath_is_empty(IntPtr ptr); + public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index 746612477d..5cb678c280 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -1,54 +1,78 @@ using System; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.Bridge; +using Godot.NativeInterop; namespace Godot { public partial class Object : IDisposable { private bool _disposed = false; + private static readonly Type CachedType = typeof(Object); - private static StringName nativeName = "Object"; + internal IntPtr NativePtr; + private bool _memoryOwn; - internal IntPtr ptr; - internal bool memoryOwn; + private WeakReference<Object> _weakReferenceToSelf; /// <summary> /// Constructs a new <see cref="Object"/>. /// </summary> public Object() : this(false) { - if (ptr == IntPtr.Zero) - ptr = godot_icall_Object_Ctor(this); - _InitializeGodotScriptInstanceInternals(); + unsafe + { + _ConstructAndInitialize(NativeCtor, NativeName, CachedType, refCounted: false); + } } - internal void _InitializeGodotScriptInstanceInternals() + internal unsafe void _ConstructAndInitialize( + delegate* unmanaged<IntPtr> nativeCtor, + StringName nativeName, + Type cachedType, + bool refCounted + ) { - godot_icall_Object_ConnectEventSignals(ptr); + if (NativePtr == IntPtr.Zero) + { + NativePtr = nativeCtor(); + + InteropUtils.TieManagedToUnmanaged(this, NativePtr, + nativeName, refCounted, GetType(), cachedType); + } + else + { + InteropUtils.TieManagedToUnmanagedWithPreSetup(this, NativePtr, + GetType(), cachedType); + } + + _weakReferenceToSelf = DisposablesTracker.RegisterGodotObject(this); } internal Object(bool memoryOwn) { - this.memoryOwn = memoryOwn; + _memoryOwn = memoryOwn; } /// <summary> /// The pointer to the native instance of this <see cref="Object"/>. /// </summary> - public IntPtr NativeInstance - { - get { return ptr; } - } + public IntPtr NativeInstance => NativePtr; internal static IntPtr GetPtr(Object instance) { if (instance == null) return IntPtr.Zero; - if (instance._disposed) + // We check if NativePtr is null because this may be called by the debugger. + // If the debugger puts a breakpoint in one of the base constructors, before + // NativePtr is assigned, that would result in UB or crashes when calling + // native functions that receive the pointer, which can happen because the + // debugger calls ToString() and tries to get the value of properties. + if (instance._disposed || instance.NativePtr == IntPtr.Zero) throw new ObjectDisposedException(instance.GetType().FullName); - return instance.ptr; + return instance.NativePtr; } ~Object() @@ -73,22 +97,35 @@ namespace Godot if (_disposed) return; - if (ptr != IntPtr.Zero) + _disposed = true; + + if (NativePtr != IntPtr.Zero) { - if (memoryOwn) + IntPtr gcHandleToFree = NativeFuncs.godotsharp_internal_object_get_associated_gchandle(NativePtr); + + if (gcHandleToFree != IntPtr.Zero) + { + object target = GCHandle.FromIntPtr(gcHandleToFree).Target; + // The GC handle may have been replaced in another thread. Release it only if + // it's associated to this managed instance, or if the target is no longer alive. + if (target != this && target != null) + gcHandleToFree = IntPtr.Zero; + } + + if (_memoryOwn) { - memoryOwn = false; - godot_icall_RefCounted_Disposed(this, ptr, !disposing); + NativeFuncs.godotsharp_internal_refcounted_disposed(NativePtr, gcHandleToFree, + (!disposing).ToGodotBool()); } else { - godot_icall_Object_Disposed(this, ptr); + NativeFuncs.godotsharp_internal_object_disposed(NativePtr, gcHandleToFree); } - ptr = IntPtr.Zero; + NativePtr = IntPtr.Zero; } - _disposed = true; + DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf); } /// <summary> @@ -97,7 +134,9 @@ namespace Godot /// <returns>A string representation of this object.</returns> public override string ToString() { - return godot_icall_Object_ToString(GetPtr(this)); + NativeFuncs.godotsharp_object_to_string(GetPtr(this), out godot_string str); + using (str) + return Marshaling.ConvertStringToManaged(str); } /// <summary> @@ -132,33 +171,72 @@ namespace Godot return new SignalAwaiter(source, signal, this); } - /// <summary> - /// Gets a new <see cref="DynamicGodotObject"/> associated with this instance. - /// </summary> - public dynamic DynamicObject => new DynamicGodotObject(this); + internal static Type InternalGetClassNativeBase(Type t) + { + do + { + var assemblyName = t.Assembly.GetName(); - internal static IntPtr __ClassDB_get_method(StringName type, string method) + if (assemblyName.Name == "GodotSharp") + return t; + + if (assemblyName.Name == "GodotSharpEditor") + return t; + } while ((t = t.BaseType) != null); + + return null; + } + + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + return false; + } + + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + value = default; + return false; + } + + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal, + NativeVariantPtrArgs args, int argCount) { - return godot_icall_Object_ClassDB_get_method(StringName.GetPtr(type), method); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Object_Ctor(Object obj); + internal static IntPtr ClassDB_get_method(StringName type, StringName method) + { + var typeSelf = (godot_string_name)type.NativeValue; + var methodSelf = (godot_string_name)method.NativeValue; + IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method(typeSelf, methodSelf); + + if (methodBind == IntPtr.Zero) + throw new NativeMethodBindNotFoundException(type + "." + method); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Object_Disposed(Object obj, IntPtr ptr); + return methodBind; + } + + internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) + { + // for some reason the '??' operator doesn't support 'delegate*' + var typeSelf = (godot_string_name)type.NativeValue; + var nativeConstructor = NativeFuncs.godotsharp_get_class_constructor(typeSelf); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_RefCounted_Disposed(Object obj, IntPtr ptr, bool isFinalizer); + if (nativeConstructor == null) + throw new NativeConstructorNotFoundException(type); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Object_ConnectEventSignals(IntPtr obj); + return nativeConstructor; + } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_Object_ToString(IntPtr ptr); + protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info) + { + } - // Used by the generated API - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Object_ClassDB_get_method(IntPtr type, string method); + // TODO: Should this be a constructor overload? + protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info) + { + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.exceptions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.exceptions.cs new file mode 100644 index 0000000000..eb2811c73d --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.exceptions.cs @@ -0,0 +1,135 @@ +using System; + +#nullable enable + +namespace Godot +{ + public partial class Object + { + public class NativeMemberNotFoundException : Exception + { + public NativeMemberNotFoundException() + { + } + + public NativeMemberNotFoundException(string? message) : base(message) + { + } + + public NativeMemberNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + } + + public class NativeConstructorNotFoundException : NativeMemberNotFoundException + { + private readonly string? _nativeClassName; + + // ReSharper disable once InconsistentNaming + private const string Arg_NativeConstructorNotFoundException = "Unable to find the native constructor."; + + public NativeConstructorNotFoundException() + : base(Arg_NativeConstructorNotFoundException) + { + } + + public NativeConstructorNotFoundException(string? nativeClassName) + : this(Arg_NativeConstructorNotFoundException, nativeClassName) + { + } + + public NativeConstructorNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + + public NativeConstructorNotFoundException(string? message, string? nativeClassName) + : base(message) + { + _nativeClassName = nativeClassName; + } + + public NativeConstructorNotFoundException(string? message, string? nativeClassName, Exception? innerException) + : base(message, innerException) + { + _nativeClassName = nativeClassName; + } + + public override string Message + { + get + { + string s = base.Message; + + if (string.IsNullOrEmpty(s)) + { + s = Arg_NativeConstructorNotFoundException; + } + + if (!string.IsNullOrEmpty(_nativeClassName)) + { + s += " " + string.Format("(Class '{0}')", _nativeClassName); + } + + return s; + } + } + } + + public class NativeMethodBindNotFoundException : NativeMemberNotFoundException + { + private readonly string? _nativeMethodName; + + // ReSharper disable once InconsistentNaming + private const string Arg_NativeMethodBindNotFoundException = "Unable to find the native method bind."; + + public NativeMethodBindNotFoundException() + : base(Arg_NativeMethodBindNotFoundException) + { + } + + public NativeMethodBindNotFoundException(string? nativeMethodName) + : this(Arg_NativeMethodBindNotFoundException, nativeMethodName) + { + } + + public NativeMethodBindNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + + public NativeMethodBindNotFoundException(string? message, string? nativeMethodName) + : base(message) + { + _nativeMethodName = nativeMethodName; + } + + public NativeMethodBindNotFoundException(string? message, string? nativeMethodName, Exception? innerException) + : base(message, innerException) + { + _nativeMethodName = nativeMethodName; + } + + public override string Message + { + get + { + string s = base.Message; + + if (string.IsNullOrEmpty(s)) + { + s = Arg_NativeMethodBindNotFoundException; + } + + if (!string.IsNullOrEmpty(_nativeMethodName)) + { + s += " " + string.Format("(Method '{0}')", _nativeMethodName); + } + + return s; + } + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index fd97a71e47..50832d7679 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index d774021131..d62cb6b0fa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -44,22 +39,22 @@ namespace Godot } /// <summary> - /// The projections's X column. Also accessible by using the index position <c>[0]</c>. + /// The projection's X column. Also accessible by using the index position <c>[0]</c>. /// </summary> public Vector4 x; /// <summary> - /// The projections's Y column. Also accessible by using the index position <c>[1]</c>. + /// The projection's Y column. Also accessible by using the index position <c>[1]</c>. /// </summary> public Vector4 y; /// <summary> - /// The projections's Z column. Also accessible by using the index position <c>[2]</c>. + /// The projection's Z column. Also accessible by using the index position <c>[2]</c>. /// </summary> public Vector4 z; /// <summary> - /// The projections's W column. Also accessible by using the index position <c>[3]</c>. + /// The projection's W column. Also accessible by using the index position <c>[3]</c>. /// </summary> public Vector4 w; @@ -79,18 +74,6 @@ namespace Godot } /// <summary> - /// Constructs a new <see cref="Projection"/> from an existing <see cref="Projection"/>. - /// </summary> - /// <param name="proj">The existing <see cref="Projection"/>.</param> - public Projection(Projection proj) - { - x = proj.x; - y = proj.y; - z = proj.z; - w = proj.w; - } - - /// <summary> /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>. /// </summary> /// <param name="transform">The <see cref="Transform3D"/>.</param> @@ -360,7 +343,7 @@ namespace Godot public int GetPixelsPerMeter(int forPixelWidth) { - Vector3 result = Xform(new Vector3(1, 0, -1)); + Vector3 result = this * new Vector3(1, 0, -1); return (int)((result.x * (real_t)0.5 + (real_t)0.5) * forPixelWidth); } @@ -593,19 +576,51 @@ namespace Godot } /// <summary> - /// Returns a vector transformed (multiplied) by this projection. + /// Returns a Vector4 transformed (multiplied) by the projection. + /// </summary> + /// <param name="proj">The projection to apply.</param> + /// <param name="vector">A Vector4 to transform.</param> + /// <returns>The transformed Vector4.</returns> + public static Vector4 operator *(Projection proj, Vector4 vector) + { + return new Vector4( + proj.x.x * vector.x + proj.y.x * vector.y + proj.z.x * vector.z + proj.w.x * vector.w, + proj.x.y * vector.x + proj.y.y * vector.y + proj.z.y * vector.z + proj.w.y * vector.w, + proj.x.z * vector.x + proj.y.z * vector.y + proj.z.z * vector.z + proj.w.z * vector.w, + proj.x.w * vector.x + proj.y.w * vector.y + proj.z.w * vector.z + proj.w.w * vector.w + ); + } + + /// <summary> + /// Returns a Vector4 transformed (multiplied) by the inverse projection. /// </summary> /// <param name="proj">The projection to apply.</param> - /// <param name="v">A vector to transform.</param> - /// <returns>The transformed vector.</returns> - public static Vector4 operator *(Projection proj, Vector4 v) + /// <param name="vector">A Vector4 to transform.</param> + /// <returns>The inversely transformed Vector4.</returns> + public static Vector4 operator *(Vector4 vector, Projection proj) { return new Vector4( - proj.x.x * v.x + proj.y.x * v.y + proj.z.x * v.z + proj.w.x * v.w, - proj.x.y * v.x + proj.y.y * v.y + proj.z.y * v.z + proj.w.y * v.w, - proj.x.z * v.x + proj.y.z * v.y + proj.z.z * v.z + proj.w.z * v.w, - proj.x.w * v.x + proj.y.w * v.y + proj.z.w * v.z + proj.w.w * v.w + proj.x.x * vector.x + proj.x.y * vector.y + proj.x.z * vector.z + proj.x.w * vector.w, + proj.y.x * vector.x + proj.y.y * vector.y + proj.y.z * vector.z + proj.y.w * vector.w, + proj.z.x * vector.x + proj.z.y * vector.y + proj.z.z * vector.z + proj.z.w * vector.w, + proj.w.x * vector.x + proj.w.y * vector.y + proj.w.z * vector.z + proj.w.w * vector.w + ); + } + + /// <summary> + /// Returns a Vector3 transformed (multiplied) by the projection. + /// </summary> + /// <param name="proj">The projection to apply.</param> + /// <param name="vector">A Vector3 to transform.</param> + /// <returns>The transformed Vector3.</returns> + public static Vector3 operator *(Projection proj, Vector3 vector) + { + Vector3 ret = new Vector3( + proj.x.x * vector.x + proj.y.x * vector.y + proj.z.x * vector.z + proj.w.x, + proj.x.y * vector.x + proj.y.y * vector.y + proj.z.y * vector.z + proj.w.y, + proj.x.z * vector.x + proj.y.z * vector.y + proj.z.z * vector.z + proj.w.z ); + return ret / (proj.x.w * vector.x + proj.y.w * vector.y + proj.z.w * vector.z + proj.w.w); } /// <summary> @@ -719,21 +734,6 @@ namespace Godot } } - /// <summary> - /// Returns a vector transformed (multiplied) by this projection. - /// </summary> - /// <param name="v">A vector to transform.</param> - /// <returns>The transformed vector.</returns> - private Vector3 Xform(Vector3 v) - { - Vector3 ret = new Vector3( - x.x * v.x + y.x * v.y + z.x * v.z + w.x, - x.y * v.x + y.y * v.y + z.y * v.z + w.y, - x.z * v.x + y.z * v.y + z.z * v.z + w.z - ); - return ret / (x.w * v.x + y.w * v.y + z.w * v.z + w.w); - } - // Constants private static readonly Projection _zero = new Projection( new Vector4(0, 0, 0, 0), diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index e38dca414f..658a14ca1d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -73,7 +68,7 @@ namespace Godot case 3: return w; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -93,7 +88,7 @@ namespace Godot w = value; break; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } @@ -318,24 +313,6 @@ namespace Godot ); } - /// <summary> - /// Returns a vector transformed (multiplied) by this quaternion. - /// </summary> - /// <param name="v">A vector to transform.</param> - /// <returns>The transformed vector.</returns> - public Vector3 Xform(Vector3 v) - { -#if DEBUG - if (!IsNormalized()) - { - throw new InvalidOperationException("Quaternion is not normalized"); - } -#endif - var u = new Vector3(x, y, z); - Vector3 uv = u.Cross(v); - return v + (((uv * w) + u.Cross(uv)) * 2); - } - // Constants private static readonly Quaternion _identity = new Quaternion(0, 0, 0, 1); @@ -363,15 +340,6 @@ namespace Godot } /// <summary> - /// Constructs a <see cref="Quaternion"/> from the given <see cref="Quaternion"/>. - /// </summary> - /// <param name="q">The existing quaternion.</param> - public Quaternion(Quaternion q) - { - this = q; - } - - /// <summary> /// Constructs a <see cref="Quaternion"/> from the given <see cref="Basis"/>. /// </summary> /// <param name="basis">The <see cref="Basis"/> to construct from.</param> @@ -466,6 +434,36 @@ namespace Godot } /// <summary> + /// Returns a Vector3 rotated (multiplied) by the quaternion. + /// </summary> + /// <param name="quaternion">The quaternion to rotate by.</param> + /// <param name="vector">A Vector3 to transform.</param> + /// <returns>The rotated Vector3.</returns> + public static Vector3 operator *(Quaternion quaternion, Vector3 vector) + { +#if DEBUG + if (!quaternion.IsNormalized()) + { + throw new InvalidOperationException("Quaternion is not normalized"); + } +#endif + var u = new Vector3(quaternion.x, quaternion.y, quaternion.z); + Vector3 uv = u.Cross(vector); + return vector + (((uv * quaternion.w) + u.Cross(uv)) * 2); + } + + /// <summary> + /// Returns a Vector3 rotated (multiplied) by the inverse quaternion. + /// </summary> + /// <param name="vector">A Vector3 to inversely rotate.</param> + /// <param name="quaternion">The quaternion to rotate by.</param> + /// <returns>The inversely rotated Vector3.</returns> + public static Vector3 operator *(Vector3 vector, Quaternion quaternion) + { + return quaternion.Inverse() * vector; + } + + /// <summary> /// Adds each component of the left <see cref="Quaternion"/> /// to the right <see cref="Quaternion"/>. This operation is not /// meaningful on its own, but it can be used as a part of a @@ -508,38 +506,6 @@ namespace Godot } /// <summary> - /// Rotates (multiplies) the <see cref="Vector3"/> - /// by the given <see cref="Quaternion"/>. - /// </summary> - /// <param name="quat">The quaternion to rotate by.</param> - /// <param name="vec">The vector to rotate.</param> - /// <returns>The rotated vector.</returns> - public static Vector3 operator *(Quaternion quat, Vector3 vec) - { -#if DEBUG - if (!quat.IsNormalized()) - { - throw new InvalidOperationException("Quaternion is not normalized."); - } -#endif - var u = new Vector3(quat.x, quat.y, quat.z); - Vector3 uv = u.Cross(vec); - return vec + (((uv * quat.w) + u.Cross(uv)) * 2); - } - - /// <summary> - /// Inversely rotates (multiplies) the <see cref="Vector3"/> - /// by the given <see cref="Quaternion"/>. - /// </summary> - /// <param name="vec">The vector to rotate.</param> - /// <param name="quat">The quaternion to rotate by.</param> - /// <returns>The inversely rotated vector.</returns> - public static Vector3 operator *(Vector3 vec, Quaternion quat) - { - return quat.Inverse() * vec; - } - - /// <summary> /// Multiplies each component of the <see cref="Quaternion"/> /// by the given <see cref="real_t"/>. This operation is not /// meaningful on its own, but it can be used as a part of a diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs index 1588869ec0..a31fef8360 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs @@ -1,5 +1,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { @@ -9,99 +11,32 @@ namespace Godot /// resource by themselves. They are used by and with the low-level Server /// classes such as <see cref="RenderingServer"/>. /// </summary> - public sealed partial class RID : IDisposable + [StructLayout(LayoutKind.Sequential)] + public struct RID { - private bool _disposed = false; + private ulong _id; // Default is 0 - internal IntPtr ptr; - - internal static IntPtr GetPtr(RID instance) - { - if (instance == null) - throw new NullReferenceException($"The instance of type {nameof(RID)} is null."); - - if (instance._disposed) - throw new ObjectDisposedException(instance.GetType().FullName); - - return instance.ptr; - } - - ~RID() - { - Dispose(false); - } - - /// <summary> - /// Disposes of this <see cref="RID"/>. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (_disposed) - return; - - if (ptr != IntPtr.Zero) - { - godot_icall_RID_Dtor(ptr); - ptr = IntPtr.Zero; - } - - _disposed = true; - } - - internal RID(IntPtr ptr) + internal RID(ulong id) { - this.ptr = ptr; - } - - /// <summary> - /// The pointer to the native instance of this <see cref="RID"/>. - /// </summary> - public IntPtr NativeInstance - { - get { return ptr; } - } - - internal RID() - { - this.ptr = IntPtr.Zero; + _id = id; } /// <summary> /// Constructs a new <see cref="RID"/> for the given <see cref="Object"/> <paramref name="from"/>. /// </summary> public RID(Object from) - { - this.ptr = godot_icall_RID_Ctor(Object.GetPtr(from)); - } + => _id = from is Resource res ? res.GetRid()._id : default; /// <summary> /// Returns the ID of the referenced resource. /// </summary> /// <returns>The ID of the referenced resource.</returns> - public int GetId() - { - return godot_icall_RID_get_id(GetPtr(this)); - } + public ulong Id => _id; /// <summary> /// Converts this <see cref="RID"/> to a string. /// </summary> /// <returns>A string representation of this RID.</returns> - public override string ToString() => "[RID]"; - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_RID_Ctor(IntPtr from); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_RID_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_RID_get_id(IntPtr ptr); + public override string ToString() => $"RID({Id})"; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index ec16920fed..d2c9b0ca8b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs new file mode 100644 index 0000000000..ee605f8d8f --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs @@ -0,0 +1,16 @@ +using System; +using System.Linq; + +#nullable enable + +namespace Godot; + +internal class ReflectionUtils +{ + public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName) + { + return AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == assemblyName)? + .GetType(typeFullName); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs index 2ba0493002..96fb891086 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs @@ -1,50 +1,67 @@ using System; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { - public class SignalAwaiter : IAwaiter<object[]>, IAwaitable<object[]> + public class SignalAwaiter : IAwaiter<Variant[]>, IAwaitable<Variant[]> { private bool _completed; - private object[] _result; - private Action _action; + private Variant[] _result; + private Action _continuation; public SignalAwaiter(Object source, StringName signal, Object target) { - godot_icall_SignalAwaiter_connect(Object.GetPtr(source), StringName.GetPtr(signal), Object.GetPtr(target), this); + var awaiterGcHandle = CustomGCHandle.AllocStrong(this); + using godot_string_name signalSrc = NativeFuncs.godotsharp_string_name_new_copy( + (godot_string_name)(signal?.NativeValue ?? default)); + NativeFuncs.godotsharp_internal_signal_awaiter_connect(Object.GetPtr(source), in signalSrc, + Object.GetPtr(target), GCHandle.ToIntPtr(awaiterGcHandle)); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter); + public bool IsCompleted => _completed; - public bool IsCompleted + public void OnCompleted(Action continuation) { - get - { - return _completed; - } + _continuation = continuation; } - public void OnCompleted(Action action) - { - this._action = action; - } + public Variant[] GetResult() => _result; - public object[] GetResult() - { - return _result; - } + public IAwaiter<Variant[]> GetAwaiter() => this; - public IAwaiter<object[]> GetAwaiter() + [UnmanagedCallersOnly] + internal static unsafe void SignalCallback(IntPtr awaiterGCHandlePtr, godot_variant** args, int argCount, + godot_bool* outAwaiterIsNull) { - return this; - } + try + { + var awaiter = (SignalAwaiter)GCHandle.FromIntPtr(awaiterGCHandlePtr).Target; - internal void SignalCallback(object[] args) - { - _completed = true; - _result = args; - _action?.Invoke(); + if (awaiter == null) + { + *outAwaiterIsNull = godot_bool.True; + return; + } + + *outAwaiterIsNull = godot_bool.False; + + awaiter._completed = true; + + Variant[] signalArgs = new Variant[argCount]; + + for (int i = 0; i < argCount; i++) + signalArgs[i] = Variant.CreateCopyingBorrowed(*args[i]); + + awaiter._result = signalArgs; + + awaiter._continuation?.Invoke(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outAwaiterIsNull = godot_bool.False; + } } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs index da01300586..3f50df0a0d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs @@ -3,7 +3,7 @@ namespace Godot /// <summary> /// Represents a signal defined in an object. /// </summary> - public struct SignalInfo + public readonly struct SignalInfo { private readonly Object _owner; private readonly StringName _signalName; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index a1f058ffe5..f0bc5949df 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Text.RegularExpressions; +using Godot.NativeInterop; + +#nullable enable namespace Godot { @@ -177,6 +179,7 @@ namespace Godot { return 0; } + if (from == 0 && to == len) { str = instance; @@ -214,7 +217,7 @@ namespace Godot /// <returns>The escaped string.</returns> public static string CEscape(this string instance) { - var sb = new StringBuilder(string.Copy(instance)); + var sb = new StringBuilder(instance); sb.Replace("\\", "\\\\"); sb.Replace("\a", "\\a"); @@ -239,7 +242,7 @@ namespace Godot /// <returns>The unescaped string.</returns> public static string CUnescape(this string instance) { - var sb = new StringBuilder(string.Copy(instance)); + var sb = new StringBuilder(instance); sb.Replace("\\a", "\a"); sb.Replace("\\b", "\b"); @@ -471,7 +474,8 @@ namespace Godot /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int Find(this string instance, string what, int from = 0, bool caseSensitive = true) { - return instance.IndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return instance.IndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -490,7 +494,8 @@ namespace Godot { // TODO: Could be more efficient if we get a char version of `IndexOf`. // See https://github.com/dotnet/runtime/issues/44116 - return instance.IndexOf(what.ToString(), from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return instance.IndexOf(what.ToString(), from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary>Find the last occurrence of a substring.</summary> @@ -519,7 +524,8 @@ namespace Godot /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) { - return instance.LastIndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return instance.LastIndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -804,6 +810,7 @@ namespace Godot { match = instance[source] == text[target]; } + if (match) { source++; @@ -926,7 +933,7 @@ namespace Godot /// <returns>The escaped string.</returns> public static string JSONEscape(this string instance) { - var sb = new StringBuilder(string.Copy(instance)); + var sb = new StringBuilder(instance); sb.Replace("\\", "\\\\"); sb.Replace("\b", "\\b"); @@ -1015,15 +1022,18 @@ namespace Godot switch (expr[0]) { case '*': - return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && ExprMatch(instance.Substring(1), expr, caseSensitive)); + return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && + ExprMatch(instance.Substring(1), expr, caseSensitive)); case '?': - return instance.Length > 0 && instance[0] != '.' && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); + return instance.Length > 0 && instance[0] != '.' && + ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); default: if (instance.Length == 0) return false; if (caseSensitive) return instance[0] == expr[0]; - return (char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); + return (char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && + ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); } } @@ -1070,12 +1080,12 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static byte[] MD5Buffer(this string instance) { - return godot_icall_String_md5_buffer(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_md5_buffer(instanceStr, out var md5Buffer); + using (md5Buffer) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(md5Buffer); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern byte[] godot_icall_String_md5_buffer(string str); - /// <summary> /// Returns the MD5 hash of the string as a string. /// </summary> @@ -1084,12 +1094,12 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static string MD5Text(this string instance) { - return godot_icall_String_md5_text(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_md5_text(instanceStr, out var md5Text); + using (md5Text) + return Marshaling.ConvertStringToManaged(md5Text); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_String_md5_text(string str); - /// <summary> /// Perform a case-insensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> @@ -1244,12 +1254,11 @@ namespace Godot /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFind(this string instance, string what, int from = -1) { - return godot_icall_String_rfind(instance, what, from); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + using godot_string whatStr = Marshaling.ConvertStringToNative(instance); + return NativeFuncs.godotsharp_string_rfind(instanceStr, whatStr, from); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_String_rfind(string str, string what, int from); - /// <summary> /// Perform a search for a substring, but start from the end of the string instead of the beginning. /// Also search case-insensitive. @@ -1261,12 +1270,11 @@ namespace Godot /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFindN(this string instance, string what, int from = -1) { - return godot_icall_String_rfindn(instance, what, from); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + using godot_string whatStr = Marshaling.ConvertStringToNative(instance); + return NativeFuncs.godotsharp_string_rfindn(instanceStr, whatStr, from); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_String_rfindn(string str, string what, int from); - /// <summary> /// Returns the right side of the string from a given position. /// </summary> @@ -1321,12 +1329,12 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static byte[] SHA256Buffer(this string instance) { - return godot_icall_String_sha256_buffer(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_sha256_buffer(instanceStr, out var sha256Buffer); + using (sha256Buffer) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(sha256Buffer); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern byte[] godot_icall_String_sha256_buffer(string str); - /// <summary> /// Returns the SHA-256 hash of the string as a string. /// </summary> @@ -1335,12 +1343,12 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static string SHA256Text(this string instance) { - return godot_icall_String_sha256_text(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_sha256_text(instanceStr, out var sha256Text); + using (sha256Text) + return Marshaling.ConvertStringToManaged(sha256Text); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_String_sha256_text(string str); - /// <summary> /// Returns the similarity index of the text compared to this string. /// 1 means totally similar and 0 means totally dissimilar. @@ -1355,6 +1363,7 @@ namespace Godot // Equal strings are totally similar return 1.0f; } + if (instance.Length < 2 || text.Length < 2) { // No way to calculate similarity without a single bigram @@ -1390,12 +1399,12 @@ namespace Godot /// </summary> public static string SimplifyPath(this string instance) { - return godot_icall_String_simplify_path(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_simplify_path(instanceStr, out godot_string simplifiedPath); + using (simplifiedPath) + return Marshaling.ConvertStringToManaged(simplifiedPath); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_String_simplify_path(string str); - /// <summary> /// Split the string by a divisor string, return an array of the substrings. /// Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". @@ -1409,7 +1418,8 @@ namespace Godot /// <returns>The array of strings split from the string.</returns> public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { - return instance.Split(new[] { divisor }, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); + return instance.Split(new[] { divisor }, + allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); } /// <summary> @@ -1605,9 +1615,9 @@ namespace Godot /// <seealso cref="XMLEscape(string)"/> /// <param name="instance">The string to unescape.</param> /// <returns>The unescaped string.</returns> - public static string XMLUnescape(this string instance) + public static string? XMLUnescape(this string instance) { - return SecurityElement.FromString(instance).Text; + return SecurityElement.FromString(instance)?.Text; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs index b1d504410b..b9ee0bc278 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -10,20 +10,11 @@ namespace Godot /// Comparing them is much faster than with regular strings, because only the pointers are compared, /// not the whole strings. /// </summary> - public sealed partial class StringName : IDisposable + public sealed class StringName : IDisposable { - private IntPtr ptr; + internal godot_string_name.movable NativeValue; - 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; - } + private WeakReference<IDisposable> _weakReferenceToSelf; ~StringName() { @@ -39,35 +30,45 @@ namespace Godot GC.SuppressFinalize(this); } - private void Dispose(bool disposing) + public void Dispose(bool disposing) { - if (ptr != IntPtr.Zero) + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) { - godot_icall_StringName_Dtor(ptr); - ptr = IntPtr.Zero; + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); } } - internal StringName(IntPtr ptr) + private StringName(godot_string_name nativeValueToOwn) { - this.ptr = ptr; + NativeValue = (godot_string_name.movable)nativeValueToOwn; + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } + // Explicit name to make it very clear + internal static StringName CreateTakingOwnershipOfDisposableValue(godot_string_name nativeValueToOwn) + => new StringName(nativeValueToOwn); + /// <summary> /// Constructs an empty <see cref="StringName"/>. /// </summary> public StringName() { - ptr = IntPtr.Zero; } /// <summary> - /// Constructs a <see cref="StringName"/> from the given <paramref name="path"/> string. + /// Constructs a <see cref="StringName"/> from the given <paramref name="name"/> string. /// </summary> - /// <param name="path">String to construct the <see cref="StringName"/> from.</param> - public StringName(string path) + /// <param name="name">String to construct the <see cref="StringName"/> from.</param> + public StringName(string name) { - ptr = path == null ? IntPtr.Zero : godot_icall_StringName_Ctor(path); + if (!string.IsNullOrEmpty(name)) + { + NativeValue = (godot_string_name.movable)NativeFuncs.godotsharp_string_name_new_from_string(name); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } } /// <summary> @@ -80,7 +81,7 @@ namespace Godot /// Converts a <see cref="StringName"/> to a string. /// </summary> /// <param name="from">The <see cref="StringName"/> to convert.</param> - public static implicit operator string(StringName from) => from.ToString(); + public static implicit operator string(StringName from) => from?.ToString(); /// <summary> /// Converts this <see cref="StringName"/> to a string. @@ -88,28 +89,75 @@ namespace Godot /// <returns>A string representation of this <see cref="StringName"/>.</returns> public override string ToString() { - return ptr == IntPtr.Zero ? string.Empty : godot_icall_StringName_operator_String(GetPtr(this)); + if (IsEmpty) + return string.Empty; + + var src = (godot_string_name)NativeValue; + NativeFuncs.godotsharp_string_name_as_string(out godot_string dest, src); + using (dest) + return Marshaling.ConvertStringToManaged(dest); } /// <summary> /// Check whether this <see cref="StringName"/> is empty. /// </summary> /// <returns>If the <see cref="StringName"/> is empty.</returns> - public bool IsEmpty() + public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty; + + public static bool operator ==(StringName left, StringName right) { - return ptr == IntPtr.Zero || godot_icall_StringName_is_empty(GetPtr(this)); + if (left is null) + return right is null; + return left.Equals(right); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr godot_icall_StringName_Ctor(string path); + public static bool operator !=(StringName left, StringName right) + { + return !(left == right); + } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void godot_icall_StringName_Dtor(IntPtr ptr); + public bool Equals(StringName other) + { + if (other is null) + return false; + return NativeValue.DangerousSelfRef == other.NativeValue.DangerousSelfRef; + } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_StringName_operator_String(IntPtr ptr); + public static bool operator ==(StringName left, in godot_string_name right) + { + if (left is null) + return right.IsEmpty; + return left.Equals(right); + } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool godot_icall_StringName_is_empty(IntPtr ptr); + public static bool operator !=(StringName left, in godot_string_name right) + { + return !(left == right); + } + + public static bool operator ==(in godot_string_name left, StringName right) + { + return right == left; + } + + public static bool operator !=(in godot_string_name left, StringName right) + { + return !(right == left); + } + + public bool Equals(in godot_string_name other) + { + return NativeValue.DangerousSelfRef == other; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || (obj is StringName other && Equals(other)); + } + + public override int GetHashCode() + { + return NativeValue.GetHashCode(); + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 68d097eb4e..70cf8bbe22 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -93,7 +88,7 @@ namespace Godot case 2: return origin; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } set @@ -110,7 +105,7 @@ namespace Godot origin = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } } @@ -389,31 +384,6 @@ namespace Godot return copy; } - /// <summary> - /// Returns a vector transformed (multiplied) by this transformation matrix. - /// </summary> - /// <seealso cref="XformInv(Vector2)"/> - /// <param name="v">A vector to transform.</param> - /// <returns>The transformed vector.</returns> - [Obsolete("Xform is deprecated. Use the multiplication operator (Transform2D * Vector2) instead.")] - public Vector2 Xform(Vector2 v) - { - return new Vector2(Tdotx(v), Tdoty(v)) + origin; - } - - /// <summary> - /// Returns a vector transformed (multiplied) by the inverse transformation matrix. - /// </summary> - /// <seealso cref="Xform(Vector2)"/> - /// <param name="v">A vector to inversely transform.</param> - /// <returns>The inversely transformed vector.</returns> - [Obsolete("XformInv is deprecated. Use the multiplication operator (Vector2 * Transform2D) instead.")] - public Vector2 XformInv(Vector2 v) - { - Vector2 vInv = v - origin; - return new Vector2(x.Dot(vInv), y.Dot(vInv)); - } - // Constants private static readonly Transform2D _identity = new Transform2D(1, 0, 0, 1, 0, 0); private static readonly Transform2D _flipX = new Transform2D(-1, 0, 0, 1, 0, 0); @@ -507,7 +477,7 @@ namespace Godot } /// <summary> - /// Returns a Vector2 transformed (multiplied) by transformation matrix. + /// Returns a Vector2 transformed (multiplied) by the transformation matrix. /// </summary> /// <param name="transform">The transformation to apply.</param> /// <param name="vector">A Vector2 to transform.</param> @@ -530,7 +500,7 @@ namespace Godot } /// <summary> - /// Returns a Rect2 transformed (multiplied) by transformation matrix. + /// Returns a Rect2 transformed (multiplied) by the transformation matrix. /// </summary> /// <param name="transform">The transformation to apply.</param> /// <param name="rect">A Rect2 to transform.</param> @@ -541,7 +511,7 @@ namespace Godot Vector2 toX = transform.x * rect.Size.x; Vector2 toY = transform.y * rect.Size.y; - return new Rect2(pos, rect.Size).Expand(pos + toX).Expand(pos + toY).Expand(pos + toX + toY); + return new Rect2(pos, new Vector2()).Expand(pos + toX).Expand(pos + toY).Expand(pos + toX + toY); } /// <summary> @@ -557,11 +527,11 @@ namespace Godot Vector2 to2 = new Vector2(rect.Position.x + rect.Size.x, rect.Position.y + rect.Size.y) * transform; Vector2 to3 = new Vector2(rect.Position.x + rect.Size.x, rect.Position.y) * transform; - return new Rect2(pos, rect.Size).Expand(to1).Expand(to2).Expand(to3); + return new Rect2(pos, new Vector2()).Expand(to1).Expand(to2).Expand(to3); } /// <summary> - /// Returns a copy of the given Vector2[] transformed (multiplied) by transformation matrix. + /// Returns a copy of the given Vector2[] transformed (multiplied) by the transformation matrix. /// </summary> /// <param name="transform">The transformation to apply.</param> /// <param name="array">A Vector2[] to transform.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 9eaf4f3252..5481225e3f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -52,7 +47,7 @@ namespace Godot case 3: return origin; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } set @@ -72,7 +67,7 @@ namespace Godot origin = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } } @@ -113,7 +108,7 @@ namespace Godot public Transform3D AffineInverse() { Basis basisInv = basis.Inverse(); - return new Transform3D(basisInv, basisInv.Xform(-origin)); + return new Transform3D(basisInv, basisInv * -origin); } /// <summary> @@ -152,7 +147,7 @@ namespace Godot public Transform3D Inverse() { Basis basisTr = basis.Transposed(); - return new Transform3D(basisTr, basisTr.Xform(-origin)); + return new Transform3D(basisTr, basisTr * -origin); } /// <summary> @@ -168,7 +163,7 @@ namespace Godot /// <param name="target">The object to look at.</param> /// <param name="up">The relative up direction.</param> /// <returns>The resulting transform.</returns> - public Transform3D LookingAt(Vector3 target, Vector3 up) + public readonly Transform3D LookingAt(Vector3 target, Vector3 up) { Transform3D t = this; t.SetLookAt(origin, target, up); @@ -194,7 +189,7 @@ namespace Godot /// <param name="axis">The axis to rotate around. Must be normalized.</param> /// <param name="angle">The angle to rotate, in radians.</param> /// <returns>The rotated transformation matrix.</returns> - public Transform3D Rotated(Vector3 axis, real_t angle) + public readonly Transform3D Rotated(Vector3 axis, real_t angle) { return new Transform3D(new Basis(axis, angle), new Vector3()) * this; } @@ -291,43 +286,6 @@ namespace Godot )); } - /// <summary> - /// Returns a vector transformed (multiplied) by this transformation matrix. - /// </summary> - /// <seealso cref="XformInv(Vector3)"/> - /// <param name="v">A vector to transform.</param> - /// <returns>The transformed vector.</returns> - public Vector3 Xform(Vector3 v) - { - return new Vector3 - ( - basis.Row0.Dot(v) + origin.x, - basis.Row1.Dot(v) + origin.y, - basis.Row2.Dot(v) + origin.z - ); - } - - /// <summary> - /// Returns a vector transformed (multiplied) by the transposed transformation matrix. - /// - /// Note: This results in a multiplication by the inverse of the - /// transformation matrix only if it represents a rotation-reflection. - /// </summary> - /// <seealso cref="Xform(Vector3)"/> - /// <param name="v">A vector to inversely transform.</param> - /// <returns>The inversely transformed vector.</returns> - public Vector3 XformInv(Vector3 v) - { - Vector3 vInv = v - origin; - - return new Vector3 - ( - (basis.Row0[0] * vInv.x) + (basis.Row1[0] * vInv.y) + (basis.Row2[0] * vInv.z), - (basis.Row0[1] * vInv.x) + (basis.Row1[1] * vInv.y) + (basis.Row2[1] * vInv.z), - (basis.Row0[2] * vInv.x) + (basis.Row1[2] * vInv.y) + (basis.Row2[2] * vInv.z) - ); - } - // Constants private static readonly Transform3D _identity = new Transform3D(Basis.Identity, Vector3.Zero); private static readonly Transform3D _flipX = new Transform3D(new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1), Vector3.Zero); @@ -404,12 +362,188 @@ namespace Godot /// <returns>The composed transform.</returns> public static Transform3D operator *(Transform3D left, Transform3D right) { - left.origin = left.Xform(right.origin); + left.origin = left * right.origin; left.basis *= right.basis; return left; } /// <summary> + /// Returns a Vector3 transformed (multiplied) by the transformation matrix. + /// </summary> + /// <param name="transform">The transformation to apply.</param> + /// <param name="vector">A Vector3 to transform.</param> + /// <returns>The transformed Vector3.</returns> + public static Vector3 operator *(Transform3D transform, Vector3 vector) + { + return new Vector3 + ( + transform.basis.Row0.Dot(vector) + transform.origin.x, + transform.basis.Row1.Dot(vector) + transform.origin.y, + transform.basis.Row2.Dot(vector) + transform.origin.z + ); + } + + /// <summary> + /// Returns a Vector3 transformed (multiplied) by the transposed transformation matrix. + /// + /// Note: This results in a multiplication by the inverse of the + /// transformation matrix only if it represents a rotation-reflection. + /// </summary> + /// <param name="vector">A Vector3 to inversely transform.</param> + /// <param name="transform">The transformation to apply.</param> + /// <returns>The inversely transformed Vector3.</returns> + public static Vector3 operator *(Vector3 vector, Transform3D transform) + { + Vector3 vInv = vector - transform.origin; + + return new Vector3 + ( + (transform.basis.Row0[0] * vInv.x) + (transform.basis.Row1[0] * vInv.y) + (transform.basis.Row2[0] * vInv.z), + (transform.basis.Row0[1] * vInv.x) + (transform.basis.Row1[1] * vInv.y) + (transform.basis.Row2[1] * vInv.z), + (transform.basis.Row0[2] * vInv.x) + (transform.basis.Row1[2] * vInv.y) + (transform.basis.Row2[2] * vInv.z) + ); + } + + /// <summary> + /// Returns an AABB transformed (multiplied) by the transformation matrix. + /// </summary> + /// <param name="transform">The transformation to apply.</param> + /// <param name="aabb">An AABB to transform.</param> + /// <returns>The transformed AABB.</returns> + public static AABB operator *(Transform3D transform, AABB aabb) + { + Vector3 min = aabb.Position; + Vector3 max = aabb.Position + aabb.Size; + + Vector3 tmin = transform.origin; + Vector3 tmax = transform.origin; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + real_t e = transform.basis[i][j] * min[j]; + real_t f = transform.basis[i][j] * max[j]; + if (e < f) + { + tmin[i] += e; + tmax[i] += f; + } + else + { + tmin[i] += f; + tmax[i] += e; + } + } + } + + return new AABB(tmin, tmax - tmin); + } + + /// <summary> + /// Returns an AABB transformed (multiplied) by the inverse transformation matrix. + /// </summary> + /// <param name="aabb">An AABB to inversely transform.</param> + /// <param name="transform">The transformation to apply.</param> + /// <returns>The inversely transformed AABB.</returns> + public static AABB operator *(AABB aabb, Transform3D transform) + { + Vector3 pos = new Vector3(aabb.Position.x + aabb.Size.x, aabb.Position.y + aabb.Size.y, aabb.Position.z + aabb.Size.z) * transform; + Vector3 to1 = new Vector3(aabb.Position.x + aabb.Size.x, aabb.Position.y + aabb.Size.y, aabb.Position.z) * transform; + Vector3 to2 = new Vector3(aabb.Position.x + aabb.Size.x, aabb.Position.y, aabb.Position.z + aabb.Size.z) * transform; + Vector3 to3 = new Vector3(aabb.Position.x + aabb.Size.x, aabb.Position.y, aabb.Position.z) * transform; + Vector3 to4 = new Vector3(aabb.Position.x, aabb.Position.y + aabb.Size.y, aabb.Position.z + aabb.Size.z) * transform; + Vector3 to5 = new Vector3(aabb.Position.x, aabb.Position.y + aabb.Size.y, aabb.Position.z) * transform; + Vector3 to6 = new Vector3(aabb.Position.x, aabb.Position.y, aabb.Position.z + aabb.Size.z) * transform; + Vector3 to7 = new Vector3(aabb.Position.x, aabb.Position.y, aabb.Position.z) * transform; + + return new AABB(pos, new Vector3()).Expand(to1).Expand(to2).Expand(to3).Expand(to4).Expand(to5).Expand(to6).Expand(to7); + } + + /// <summary> + /// Returns a Plane transformed (multiplied) by the transformation matrix. + /// </summary> + /// <param name="transform">The transformation to apply.</param> + /// <param name="plane">A Plane to transform.</param> + /// <returns>The transformed Plane.</returns> + public static Plane operator *(Transform3D transform, Plane plane) + { + Basis bInvTrans = transform.basis.Inverse().Transposed(); + + // Transform a single point on the plane. + Vector3 point = transform * (plane.Normal * plane.D); + + // Use inverse transpose for correct normals with non-uniform scaling. + Vector3 normal = (bInvTrans * plane.Normal).Normalized(); + + real_t d = normal.Dot(point); + return new Plane(normal, d); + } + + /// <summary> + /// Returns a Plane transformed (multiplied) by the inverse transformation matrix. + /// </summary> + /// <param name="plane">A Plane to inversely transform.</param> + /// <param name="transform">The transformation to apply.</param> + /// <returns>The inversely transformed Plane.</returns> + public static Plane operator *(Plane plane, Transform3D transform) + { + Transform3D tInv = transform.AffineInverse(); + Basis bTrans = transform.basis.Transposed(); + + // Transform a single point on the plane. + Vector3 point = tInv * (plane.Normal * plane.D); + + // Note that instead of precalculating the transpose, an alternative + // would be to use the transpose for the basis transform. + // However that would be less SIMD friendly (requiring a swizzle). + // So the cost is one extra precalced value in the calling code. + // This is probably worth it, as this could be used in bottleneck areas. And + // where it is not a bottleneck, the non-fast method is fine. + + // Use transpose for correct normals with non-uniform scaling. + Vector3 normal = (bTrans * plane.Normal).Normalized(); + + real_t d = normal.Dot(point); + return new Plane(normal, d); + } + + /// <summary> + /// Returns a copy of the given Vector3[] transformed (multiplied) by the transformation matrix. + /// </summary> + /// <param name="transform">The transformation to apply.</param> + /// <param name="array">A Vector3[] to transform.</param> + /// <returns>The transformed copy of the Vector3[].</returns> + public static Vector3[] operator *(Transform3D transform, Vector3[] array) + { + Vector3[] newArray = new Vector3[array.Length]; + + for (int i = 0; i < array.Length; i++) + { + newArray[i] = transform * array[i]; + } + + return newArray; + } + + /// <summary> + /// Returns a copy of the given Vector3[] transformed (multiplied) by the inverse transformation matrix. + /// </summary> + /// <param name="array">A Vector3[] to inversely transform.</param> + /// <param name="transform">The transformation to apply.</param> + /// <returns>The inversely transformed copy of the Vector3[].</returns> + public static Vector3[] operator *(Vector3[] array, Transform3D transform) + { + Vector3[] newArray = new Vector3[array.Length]; + + for (int i = 0; i < array.Length; i++) + { + newArray[i] = array[i] * transform; + } + + return newArray; + } + + /// <summary> /// Returns <see langword="true"/> if the transforms are exactly equal. /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. @@ -443,7 +577,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the transform and the object are exactly equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is Transform3D) { @@ -460,7 +594,7 @@ namespace Godot /// </summary> /// <param name="other">The other transform to compare.</param> /// <returns>Whether or not the matrices are exactly equal.</returns> - public bool Equals(Transform3D other) + public readonly bool Equals(Transform3D other) { return basis.Equals(other.basis) && origin.Equals(other.origin); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs deleted file mode 100644 index eae8927ceb..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// Event arguments for when unhandled exceptions occur. - /// </summary> - public class UnhandledExceptionArgs - { - /// <summary> - /// Exception object. - /// </summary> - public Exception Exception { get; private set; } - - internal UnhandledExceptionArgs(Exception exception) - { - Exception = exception; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 67f70390dd..04c2ea7eb9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -62,7 +57,7 @@ namespace Godot case 1: return y; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -76,7 +71,7 @@ namespace Godot y = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } @@ -644,16 +639,6 @@ namespace Godot } /// <summary> - /// Constructs a new <see cref="Vector2"/> from an existing <see cref="Vector2"/>. - /// </summary> - /// <param name="v">The existing <see cref="Vector2"/>.</param> - public Vector2(Vector2 v) - { - x = v.x; - y = v.y; - } - - /// <summary> /// Creates a unit Vector2 rotated to the given angle. This is equivalent to doing /// <c>Vector2(Mathf.Cos(angle), Mathf.Sin(angle))</c> or <c>Vector2.Right.Rotated(angle)</c>. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs index b61954a84c..a8f42972d7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -62,7 +57,7 @@ namespace Godot case 1: return y; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -76,7 +71,7 @@ namespace Godot y = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } @@ -355,27 +350,6 @@ namespace Godot } /// <summary> - /// Constructs a new <see cref="Vector2i"/> from an existing <see cref="Vector2i"/>. - /// </summary> - /// <param name="vi">The existing <see cref="Vector2i"/>.</param> - public Vector2i(Vector2i vi) - { - this.x = vi.x; - this.y = vi.y; - } - - /// <summary> - /// Constructs a new <see cref="Vector2i"/> from an existing <see cref="Vector2"/> - /// by rounding the components via <see cref="Mathf.RoundToInt(real_t)"/>. - /// </summary> - /// <param name="v">The <see cref="Vector2"/> to convert.</param> - public Vector2i(Vector2 v) - { - this.x = Mathf.RoundToInt(v.x); - this.y = Mathf.RoundToInt(v.y); - } - - /// <summary> /// Adds each component of the <see cref="Vector2i"/> /// with the components of the given <see cref="Vector2i"/>. /// </summary> @@ -679,7 +653,10 @@ namespace Godot /// <param name="value">The vector to convert.</param> public static explicit operator Vector2i(Vector2 value) { - return new Vector2i(value); + return new Vector2i( + Mathf.RoundToInt(value.x), + Mathf.RoundToInt(value.y) + ); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 67a98efc2d..d5941d6b60 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -74,7 +69,7 @@ namespace Godot case 2: return z; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -91,7 +86,7 @@ namespace Godot z = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } @@ -523,7 +518,7 @@ namespace Godot throw new ArgumentException("Argument is not normalized", nameof(axis)); } #endif - return new Basis(axis, angle).Xform(this); + return new Basis(axis, angle) * this; } /// <summary> @@ -697,17 +692,6 @@ namespace Godot } /// <summary> - /// Constructs a new <see cref="Vector3"/> from an existing <see cref="Vector3"/>. - /// </summary> - /// <param name="v">The existing <see cref="Vector3"/>.</param> - public Vector3(Vector3 v) - { - x = v.x; - y = v.y; - z = v.z; - } - - /// <summary> /// Adds each component of the <see cref="Vector3"/> /// with the components of the given <see cref="Vector3"/>. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs index 0d4894f206..eb46f36e7c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -74,7 +69,7 @@ namespace Godot case 2: return z; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -91,7 +86,7 @@ namespace Godot z = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } @@ -335,29 +330,6 @@ namespace Godot } /// <summary> - /// Constructs a new <see cref="Vector3i"/> from an existing <see cref="Vector3i"/>. - /// </summary> - /// <param name="vi">The existing <see cref="Vector3i"/>.</param> - public Vector3i(Vector3i vi) - { - this.x = vi.x; - this.y = vi.y; - this.z = vi.z; - } - - /// <summary> - /// Constructs a new <see cref="Vector3i"/> from an existing <see cref="Vector3"/> - /// by rounding the components via <see cref="Mathf.RoundToInt(real_t)"/>. - /// </summary> - /// <param name="v">The <see cref="Vector3"/> to convert.</param> - public Vector3i(Vector3 v) - { - this.x = Mathf.RoundToInt(v.x); - this.y = Mathf.RoundToInt(v.y); - this.z = Mathf.RoundToInt(v.z); - } - - /// <summary> /// Adds each component of the <see cref="Vector3i"/> /// with the components of the given <see cref="Vector3i"/>. /// </summary> @@ -689,7 +661,11 @@ namespace Godot /// <param name="value">The vector to convert.</param> public static explicit operator Vector3i(Vector3 value) { - return new Vector3i(value); + return new Vector3i( + Mathf.RoundToInt(value.x), + Mathf.RoundToInt(value.y), + Mathf.RoundToInt(value.z) + ); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 4af817455c..20a24616ce 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -482,18 +477,6 @@ namespace Godot } /// <summary> - /// Constructs a new <see cref="Vector4"/> from an existing <see cref="Vector4"/>. - /// </summary> - /// <param name="v">The existing <see cref="Vector4"/>.</param> - public Vector4(Vector4 v) - { - x = v.x; - y = v.y; - z = v.z; - w = v.w; - } - - /// <summary> /// Adds each component of the <see cref="Vector4"/> /// with the components of the given <see cref="Vector4"/>. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs index 365dcef486..11c2b7234b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -263,31 +258,6 @@ namespace Godot } /// <summary> - /// Constructs a new <see cref="Vector4i"/> from an existing <see cref="Vector4i"/>. - /// </summary> - /// <param name="vi">The existing <see cref="Vector4i"/>.</param> - public Vector4i(Vector4i vi) - { - this.x = vi.x; - this.y = vi.y; - this.z = vi.z; - this.w = vi.w; - } - - /// <summary> - /// Constructs a new <see cref="Vector4i"/> from an existing <see cref="Vector4"/> - /// by rounding the components via <see cref="Mathf.RoundToInt(real_t)"/>. - /// </summary> - /// <param name="v">The <see cref="Vector4"/> to convert.</param> - public Vector4i(Vector4 v) - { - this.x = Mathf.RoundToInt(v.x); - this.y = Mathf.RoundToInt(v.y); - this.z = Mathf.RoundToInt(v.z); - this.w = Mathf.RoundToInt(v.w); - } - - /// <summary> /// Adds each component of the <see cref="Vector4i"/> /// with the components of the given <see cref="Vector4i"/>. /// </summary> @@ -643,7 +613,12 @@ namespace Godot /// <param name="value">The vector to convert.</param> public static explicit operator Vector4i(Vector4 value) { - return new Vector4i(value); + return new Vector4i( + Mathf.RoundToInt(value.x), + Mathf.RoundToInt(value.y), + Mathf.RoundToInt(value.z), + Mathf.RoundToInt(value.w) + ); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GlobalUsings.cs b/modules/mono/glue/GodotSharp/GodotSharp/GlobalUsings.cs new file mode 100644 index 0000000000..263a934fae --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/GlobalUsings.cs @@ -0,0 +1,5 @@ +#if REAL_T_IS_DOUBLE +global using real_t = System.Double; +#else +global using real_t = System.Single; +#endif diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 4f55ce47e8..aae7a5ebfa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -4,25 +4,71 @@ <OutputPath>bin/$(Configuration)</OutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <RootNamespace>Godot</RootNamespace> - <TargetFramework>netstandard2.1</TargetFramework> + <TargetFramework>net6.0</TargetFramework> <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile> <EnableDefaultItems>false</EnableDefaultItems> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <LangVersion>10</LangVersion> + + <AnalysisMode>Recommended</AnalysisMode> + + <!-- Disabled temporarily as it pollutes the warnings, but we need to document public APIs. --> + <NoWarn>CS1591</NoWarn> </PropertyGroup> <PropertyGroup> + <Description>Godot C# Core API.</Description> + <Authors>Godot Engine contributors</Authors> + + <PackageId>GodotSharp</PackageId> + <Version>4.0.0</Version> + <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> + <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl> + <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> + <PackageLicenseExpression>MIT</PackageLicenseExpression> + + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + </PropertyGroup> + <ItemGroup> + <!-- SdkPackageVersions.props for easy access --> + <None Include="$(GodotSdkPackageVersionsFilePath)"> + <Link>SdkPackageVersions.props</Link> + </None> + </ItemGroup> + <PropertyGroup> <DefineConstants>$(DefineConstants);GODOT</DefineConstants> </PropertyGroup> <ItemGroup> + <PackageReference Include="ReflectionAnalyzers" Version="0.1.22-dev" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" /> + <!--PackageReference Include="IDisposableAnalyzers" Version="3.4.13" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" /--> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + </ItemGroup> + <!-- Sources --> + <ItemGroup> <Compile Include="Core\AABB.cs" /> + <Compile Include="Core\Bridge\GodotSerializationInfo.cs" /> + <Compile Include="Core\Bridge\MethodInfo.cs" /> + <Compile Include="Core\CustomGCHandle.cs" /> <Compile Include="Core\Array.cs" /> <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" /> - <Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" /> <Compile Include="Core\Attributes\ExportAttribute.cs" /> - <Compile Include="Core\Attributes\GodotMethodAttribute.cs" /> + <Compile Include="Core\Attributes\ExportCategoryAttribute.cs" /> + <Compile Include="Core\Attributes\ExportGroupAttribute.cs" /> + <Compile Include="Core\Attributes\ExportSubgroupAttribute.cs" /> + <Compile Include="Core\Attributes\MustBeVariantAttribute.cs" /> <Compile Include="Core\Attributes\RPCAttribute.cs" /> <Compile Include="Core\Attributes\ScriptPathAttribute.cs" /> <Compile Include="Core\Attributes\SignalAttribute.cs" /> <Compile Include="Core\Attributes\ToolAttribute.cs" /> <Compile Include="Core\Basis.cs" /> + <Compile Include="Core\Bridge\CSharpInstanceBridge.cs" /> + <Compile Include="Core\Bridge\GCHandleBridge.cs" /> + <Compile Include="Core\Bridge\AlcReloadCfg.cs" /> + <Compile Include="Core\Bridge\ManagedCallbacks.cs" /> + <Compile Include="Core\Bridge\PropertyInfo.cs" /> + <Compile Include="Core\Bridge\ScriptManagerBridge.cs" /> + <Compile Include="Core\Bridge\ScriptManagerBridge.types.cs" /> <Compile Include="Core\Callable.cs" /> <Compile Include="Core\Color.cs" /> <Compile Include="Core\Colors.cs" /> @@ -30,45 +76,58 @@ <Compile Include="Core\DelegateUtils.cs" /> <Compile Include="Core\Dictionary.cs" /> <Compile Include="Core\Dispatcher.cs" /> - <Compile Include="Core\DynamicObject.cs" /> <Compile Include="Core\Extensions\NodeExtensions.cs" /> <Compile Include="Core\Extensions\ObjectExtensions.cs" /> <Compile Include="Core\Extensions\PackedSceneExtensions.cs" /> <Compile Include="Core\Extensions\ResourceLoaderExtensions.cs" /> - <Compile Include="Core\Extensions\SceneTreeExtensions.cs" /> <Compile Include="Core\GD.cs" /> <Compile Include="Core\GodotSynchronizationContext.cs" /> <Compile Include="Core\GodotTaskScheduler.cs" /> <Compile Include="Core\GodotTraceListener.cs" /> <Compile Include="Core\GodotUnhandledExceptionEvent.cs" /> + <Compile Include="Core\DisposablesTracker.cs" /> <Compile Include="Core\Interfaces\IAwaitable.cs" /> <Compile Include="Core\Interfaces\IAwaiter.cs" /> <Compile Include="Core\Interfaces\ISerializationListener.cs" /> - <Compile Include="Core\MarshalUtils.cs" /> <Compile Include="Core\Mathf.cs" /> <Compile Include="Core\MathfEx.cs" /> + <Compile Include="Core\NativeInterop\CustomUnsafe.cs" /> + <Compile Include="Core\NativeInterop\ExceptionUtils.cs" /> + <Compile Include="Core\NativeInterop\GodotDllImportResolver.cs" /> + <Compile Include="Core\NativeInterop\InteropUtils.cs" /> + <Compile Include="Core\NativeInterop\NativeFuncs.extended.cs" /> + <Compile Include="Core\NativeInterop\NativeVariantPtrArgs.cs" /> + <Compile Include="Core\NativeInterop\VariantConversionCallbacks.cs" /> + <Compile Include="Core\NativeInterop\VariantSpanHelpers.cs" /> + <Compile Include="Core\NativeInterop\VariantUtils.cs" /> <Compile Include="Core\NodePath.cs" /> <Compile Include="Core\Object.base.cs" /> + <Compile Include="Core\Object.exceptions.cs" /> <Compile Include="Core\Plane.cs" /> <Compile Include="Core\Projection.cs" /> <Compile Include="Core\Quaternion.cs" /> <Compile Include="Core\Rect2.cs" /> <Compile Include="Core\Rect2i.cs" /> + <Compile Include="Core\ReflectionUtils.cs" /> <Compile Include="Core\RID.cs" /> + <Compile Include="Core\NativeInterop\NativeFuncs.cs" /> + <Compile Include="Core\NativeInterop\InteropStructs.cs" /> + <Compile Include="Core\NativeInterop\Marshaling.cs" /> <Compile Include="Core\SignalInfo.cs" /> <Compile Include="Core\SignalAwaiter.cs" /> <Compile Include="Core\StringExtensions.cs" /> <Compile Include="Core\StringName.cs" /> <Compile Include="Core\Transform2D.cs" /> <Compile Include="Core\Transform3D.cs" /> - <Compile Include="Core\UnhandledExceptionArgs.cs" /> <Compile Include="Core\Vector2.cs" /> <Compile Include="Core\Vector2i.cs" /> <Compile Include="Core\Vector3.cs" /> <Compile Include="Core\Vector3i.cs" /> <Compile Include="Core\Vector4.cs" /> <Compile Include="Core\Vector4i.cs" /> + <Compile Include="GlobalUsings.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Variant.cs" /> </ItemGroup> <!-- We import a props file with auto-generated includes. This works well with Rider. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj.DotSettings b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj.DotSettings new file mode 100644 index 0000000000..1add6cc77e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj.DotSettings @@ -0,0 +1,5 @@ +<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated_005Cgodotobjects/@EntryIndexedValue">True</s:Boolean> +</wpf:ResourceDictionary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs new file mode 100644 index 0000000000..85ef258922 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs @@ -0,0 +1,841 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Godot.NativeInterop; + +namespace Godot; + +#nullable enable + +[SuppressMessage("ReSharper", "RedundantNameQualifier")] +public partial struct Variant : IDisposable +{ + internal godot_variant.movable NativeVar; + private object? _obj; + private Disposer? _disposer; + + private sealed class Disposer : IDisposable + { + private godot_variant.movable _native; + + private WeakReference<IDisposable>? _weakReferenceToSelf; + + public Disposer(in godot_variant.movable nativeVar) + { + _native = nativeVar; + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } + + ~Disposer() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + _native.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) + { + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); + } + } + } + + private Variant(in godot_variant nativeVar) + { + NativeVar = (godot_variant.movable)nativeVar; + _obj = null; + + switch (nativeVar.Type) + { + case Type.Nil: + case Type.Bool: + case Type.Int: + case Type.Float: + case Type.Vector2: + case Type.Vector2i: + case Type.Rect2: + case Type.Rect2i: + case Type.Vector3: + case Type.Vector3i: + case Type.Plane: + case Type.Quaternion: + case Type.Color: + case Type.Rid: + _disposer = null; + break; + default: + { + _disposer = new Disposer(NativeVar); + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // Explicit name to make it very clear + public static Variant CreateTakingOwnershipOfDisposableValue(in godot_variant nativeValueToOwn) => + new(nativeValueToOwn); + + // Explicit name to make it very clear + public static Variant CreateCopyingBorrowed(in godot_variant nativeValueToOwn) => + new(NativeFuncs.godotsharp_variant_new_copy(nativeValueToOwn)); + + /// <summary> + /// Constructs a new <see cref="Godot.NativeInterop.godot_variant"/> from this instance. + /// The caller is responsible of disposing the new instance to avoid memory leaks. + /// </summary> + public godot_variant CopyNativeVariant() => + NativeFuncs.godotsharp_variant_new_copy((godot_variant)NativeVar); + + public void Dispose() + { + _disposer?.Dispose(); + NativeVar = default; + _obj = null; + } + + // TODO: Consider renaming Variant.Type to VariantType and this property to Type. VariantType would also avoid ambiguity with System.Type. + public Type VariantType => NativeVar.DangerousSelfRef.Type; + + public override string ToString() => AsString(); + + public object? Obj + { + get + { + if (_obj == null) + _obj = Marshaling.ConvertVariantToManagedObject((godot_variant)NativeVar); + + return _obj; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool AsBool() => + VariantUtils.ConvertToBool((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public char AsChar() => + (char)VariantUtils.ConvertToUInt16((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte AsSByte() => + VariantUtils.ConvertToInt8((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short AsInt16() => + VariantUtils.ConvertToInt16((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int AsInt32() => + VariantUtils.ConvertToInt32((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long AsInt64() => + VariantUtils.ConvertToInt64((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte AsByte() => + VariantUtils.ConvertToUInt8((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort AsUInt16() => + VariantUtils.ConvertToUInt16((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint AsUInt32() => + VariantUtils.ConvertToUInt32((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong AsUInt64() => + VariantUtils.ConvertToUInt64((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float AsSingle() => + VariantUtils.ConvertToFloat32((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double AsDouble() => + VariantUtils.ConvertToFloat64((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string AsString() => + VariantUtils.ConvertToStringObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2 AsVector2() => + VariantUtils.ConvertToVector2((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2i AsVector2i() => + VariantUtils.ConvertToVector2i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rect2 AsRect2() => + VariantUtils.ConvertToRect2((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rect2i AsRect2i() => + VariantUtils.ConvertToRect2i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Transform2D AsTransform2D() => + VariantUtils.ConvertToTransform2D((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3 AsVector3() => + VariantUtils.ConvertToVector3((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3i AsVector3i() => + VariantUtils.ConvertToVector3i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Basis AsBasis() => + VariantUtils.ConvertToBasis((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Quaternion AsQuaternion() => + VariantUtils.ConvertToQuaternion((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Transform3D AsTransform3D() => + VariantUtils.ConvertToTransform3D((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 AsVector4() => + VariantUtils.ConvertToVector4((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4i AsVector4i() => + VariantUtils.ConvertToVector4i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Projection AsProjection() => + VariantUtils.ConvertToProjection((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AABB AsAABB() => + VariantUtils.ConvertToAABB((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Color AsColor() => + VariantUtils.ConvertToColor((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Plane AsPlane() => + VariantUtils.ConvertToPlane((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Callable AsCallable() => + VariantUtils.ConvertToCallableManaged((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SignalInfo AsSignalInfo() => + VariantUtils.ConvertToSignalInfo((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte[] AsByteArray() => + VariantUtils.ConvertAsPackedByteArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int[] AsInt32Array() => + VariantUtils.ConvertAsPackedInt32ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long[] AsInt64Array() => + VariantUtils.ConvertAsPackedInt64ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float[] AsFloat32Array() => + VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double[] AsFloat64Array() => + VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string[] AsStringArray() => + VariantUtils.ConvertAsPackedStringArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2[] AsVector2Array() => + VariantUtils.ConvertAsPackedVector2ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3[] AsVector3Array() => + VariantUtils.ConvertAsPackedVector3ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Color[] AsColorArray() => + VariantUtils.ConvertAsPackedColorArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T[] AsGodotObjectArray<T>() + where T : Godot.Object => + VariantUtils.ConvertToSystemArrayOfGodotObject<T>((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Dictionary<TKey, TValue> AsGodotDictionary<TKey, TValue>() => + VariantUtils.ConvertToDictionaryObject<TKey, TValue>((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Array<T> AsGodotArray<T>() => + VariantUtils.ConvertToArrayObject<T>((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StringName[] AsSystemArrayOfStringName() => + VariantUtils.ConvertToSystemArrayOfStringName((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NodePath[] AsSystemArrayOfNodePath() => + VariantUtils.ConvertToSystemArrayOfNodePath((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RID[] AsSystemArrayOfRID() => + VariantUtils.ConvertToSystemArrayOfRID((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Godot.Object AsGodotObject() => + VariantUtils.ConvertToGodotObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StringName AsStringName() => + VariantUtils.ConvertToStringNameObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NodePath AsNodePath() => + VariantUtils.ConvertToNodePathObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RID AsRID() => + VariantUtils.ConvertToRID((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Dictionary AsGodotDictionary() => + VariantUtils.ConvertToDictionaryObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Array AsGodotArray() => + VariantUtils.ConvertToArrayObject((godot_variant)NativeVar); + + // Explicit conversion operators to supported types + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator bool(Variant from) => from.AsBool(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator char(Variant from) => from.AsChar(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator sbyte(Variant from) => from.AsSByte(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator short(Variant from) => from.AsInt16(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator int(Variant from) => from.AsInt32(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator long(Variant from) => from.AsInt64(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte(Variant from) => from.AsByte(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator ushort(Variant from) => from.AsUInt16(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator uint(Variant from) => from.AsUInt32(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator ulong(Variant from) => from.AsUInt64(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator float(Variant from) => from.AsSingle(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator double(Variant from) => from.AsDouble(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator string(Variant from) => from.AsString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector2(Variant from) => from.AsVector2(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector2i(Variant from) => from.AsVector2i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Rect2(Variant from) => from.AsRect2(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Rect2i(Variant from) => from.AsRect2i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Transform2D(Variant from) => from.AsTransform2D(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector3(Variant from) => from.AsVector3(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector3i(Variant from) => from.AsVector3i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Basis(Variant from) => from.AsBasis(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Quaternion(Variant from) => from.AsQuaternion(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Transform3D(Variant from) => from.AsTransform3D(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector4(Variant from) => from.AsVector4(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector4i(Variant from) => from.AsVector4i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Projection(Variant from) => from.AsProjection(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator AABB(Variant from) => from.AsAABB(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Color(Variant from) => from.AsColor(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Plane(Variant from) => from.AsPlane(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Callable(Variant from) => from.AsCallable(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator SignalInfo(Variant from) => from.AsSignalInfo(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte[](Variant from) => from.AsByteArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator int[](Variant from) => from.AsInt32Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator long[](Variant from) => from.AsInt64Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator float[](Variant from) => from.AsFloat32Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator double[](Variant from) => from.AsFloat64Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator string[](Variant from) => from.AsStringArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector2[](Variant from) => from.AsVector2Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector3[](Variant from) => from.AsVector3Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Color[](Variant from) => from.AsColorArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator StringName[](Variant from) => from.AsSystemArrayOfStringName(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator NodePath[](Variant from) => from.AsSystemArrayOfNodePath(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator RID[](Variant from) => from.AsSystemArrayOfRID(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Godot.Object(Variant from) => from.AsGodotObject(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator StringName(Variant from) => from.AsStringName(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator NodePath(Variant from) => from.AsNodePath(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator RID(Variant from) => from.AsRID(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Collections.Dictionary(Variant from) => from.AsGodotDictionary(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Collections.Array(Variant from) => from.AsGodotArray(); + + // While we provide implicit conversion operators, normal methods are still needed for + // casts that are not done implicitly (e.g.: raw array to Span, enum to integer, etc). + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(bool from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(char from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(sbyte from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(short from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(int from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(long from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(byte from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(ushort from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(uint from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(ulong from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(float from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(double from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(string from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector2 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector2i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Rect2 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Rect2i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Transform2D from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector3 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector3i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Basis from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Quaternion from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Transform3D from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector4 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector4i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Projection from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(AABB from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Color from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Plane from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Callable from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(SignalInfo from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<byte> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<int> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<long> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<float> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<double> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<string> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<Vector2> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<Vector3> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<Color> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Godot.Object[] from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom<TKey, TValue>(Collections.Dictionary<TKey, TValue> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromDictionary(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom<T>(Collections.Array<T> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<StringName> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<NodePath> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<RID> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Godot.Object from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(StringName from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(NodePath from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(RID from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Collections.Dictionary from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Collections.Array from) => from; + + // Implicit conversion operators + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(bool from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromBool(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(char from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(sbyte from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(short from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(int from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(long from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(byte from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(ushort from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(uint from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(ulong from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(float from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromFloat(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(double from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromFloat(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(string from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromString(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector2 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector2(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector2i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector2i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Rect2 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRect2(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Rect2i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRect2i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Transform2D from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromTransform2D(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector3 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector3(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector3i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector3i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Basis from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromBasis(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Quaternion from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromQuaternion(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Transform3D from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromTransform3D(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector4 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector4(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector4i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector4i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Projection from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromProjection(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(AABB from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromAABB(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Color from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromColor(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Plane from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPlane(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Callable from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromCallable(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(SignalInfo from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSignalInfo(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<byte> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedByteArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<int> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedInt32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<long> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedInt64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<float> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedFloat32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<double> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedFloat64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<string> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedStringArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<Vector2> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedVector2Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<Vector3> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedVector3Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<Color> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedColorArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Godot.Object[] from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfGodotObject(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<StringName> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfStringName(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<NodePath> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfNodePath(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<RID> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfRID(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Godot.Object from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromGodotObject(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(StringName from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromStringName(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(NodePath from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromNodePath(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(RID from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRID(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Collections.Dictionary from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromDictionary(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Collections.Array from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from)); +} diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index a8c4ba96b5..ebf09aab7b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -4,9 +4,24 @@ <OutputPath>bin/$(Configuration)</OutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <RootNamespace>Godot</RootNamespace> - <TargetFramework>netstandard2.1</TargetFramework> + <TargetFramework>net6.0</TargetFramework> <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile> <EnableDefaultItems>false</EnableDefaultItems> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <LangVersion>10</LangVersion> + </PropertyGroup> + <PropertyGroup> + <Description>Godot C# Editor API.</Description> + <Authors>Godot Engine contributors</Authors> + + <PackageId>GodotSharpEditor</PackageId> + <Version>4.0.0</Version> + <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> + <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharpEditor</RepositoryUrl> + <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> + <PackageLicenseExpression>MIT</PackageLicenseExpression> + + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> </PropertyGroup> <PropertyGroup> <DefineConstants>$(DefineConstants);GODOT</DefineConstants> diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj.DotSettings b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj.DotSettings new file mode 100644 index 0000000000..c7ff6fd3ee --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj.DotSettings @@ -0,0 +1,4 @@ +<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated_005Cgodotobjects/@EntryIndexedValue">True</s:Boolean> +</wpf:ResourceDictionary> diff --git a/modules/mono/glue/arguments_vector.h b/modules/mono/glue/arguments_vector.h deleted file mode 100644 index 4405809887..0000000000 --- a/modules/mono/glue/arguments_vector.h +++ /dev/null @@ -1,67 +0,0 @@ -/*************************************************************************/ -/* arguments_vector.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef ARGUMENTS_VECTOR_H -#define ARGUMENTS_VECTOR_H - -#include "core/os/memory.h" - -template <typename T, int POOL_SIZE = 5> -struct ArgumentsVector { -private: - T pool[POOL_SIZE]; - T *_ptr = nullptr; - int size; - - ArgumentsVector() = delete; - ArgumentsVector(const ArgumentsVector &) = delete; - -public: - T *ptr() { return _ptr; } - T &get(int p_idx) { return _ptr[p_idx]; } - void set(int p_idx, const T &p_value) { _ptr[p_idx] = p_value; } - - explicit ArgumentsVector(int p_size) : - size(p_size) { - if (p_size <= POOL_SIZE) { - _ptr = pool; - } else { - _ptr = memnew_arr(T, p_size); - } - } - - ~ArgumentsVector() { - if (size > POOL_SIZE) { - memdelete_arr(_ptr); - } - } -}; - -#endif // ARGUMENTS_VECTOR_H diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp deleted file mode 100644 index 7b9dbc87cf..0000000000 --- a/modules/mono/glue/base_object_glue.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/*************************************************************************/ -/* base_object_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/object/class_db.h" -#include "core/object/ref_counted.h" -#include "core/string/string_name.h" - -#include "../csharp_script.h" -#include "../mono_gd/gd_mono_cache.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_internals.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" -#include "../signal_awaiter_utils.h" -#include "arguments_vector.h" - -Object *godot_icall_Object_Ctor(MonoObject *p_obj) { - Object *instance = memnew(Object); - GDMonoInternals::tie_managed_to_unmanaged(p_obj, instance); - return instance; -} - -void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_ptr == nullptr); -#endif - - if (p_ptr->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); - if (cs_instance) { - if (!cs_instance->is_destructing_script_instance()) { - cs_instance->mono_object_disposed(p_obj); - p_ptr->set_script_instance(nullptr); - } - return; - } - } - - void *data = CSharpLanguage::get_existing_instance_binding(p_ptr); - - if (data) { - CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); - if (script_binding.inited) { - MonoGCHandleData &gchandle = script_binding.gchandle; - if (!gchandle.is_released()) { - CSharpLanguage::release_script_gchandle(p_obj, gchandle); - } - } - } -} - -void godot_icall_RefCounted_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_ptr == nullptr); - // This is only called with RefCounted derived classes - CRASH_COND(!Object::cast_to<RefCounted>(p_ptr)); -#endif - - RefCounted *rc = static_cast<RefCounted *>(p_ptr); - - if (rc->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(rc->get_script_instance()); - if (cs_instance) { - if (!cs_instance->is_destructing_script_instance()) { - bool delete_owner; - bool remove_script_instance; - - cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, delete_owner, remove_script_instance); - - if (delete_owner) { - memdelete(rc); - } else if (remove_script_instance) { - rc->set_script_instance(nullptr); - } - } - return; - } - } - - // Unsafe refcount decrement. The managed instance also counts as a reference. - // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) - CSharpLanguage::get_singleton()->pre_unsafe_unreference(rc); - if (rc->unreference()) { - memdelete(rc); - } else { - void *data = CSharpLanguage::get_existing_instance_binding(rc); - - if (data) { - CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); - if (script_binding.inited) { - MonoGCHandleData &gchandle = script_binding.gchandle; - if (!gchandle.is_released()) { - CSharpLanguage::release_script_gchandle(p_obj, gchandle); - } - } - } - } -} - -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_ptr) { - if (!p_ptr) { - return nullptr; - } - - Ref<WeakRef> wref; - RefCounted *rc = Object::cast_to<RefCounted>(p_ptr); - - if (rc) { - Ref<RefCounted> r = rc; - if (!r.is_valid()) { - return nullptr; - } - - wref.instantiate(); - wref->set_ref(r); - } else { - wref.instantiate(); - wref->set_obj(p_ptr); - } - - return GDMonoUtils::unmanaged_get_managed(wref.ptr()); -} - -int32_t godot_icall_SignalAwaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, MonoObject *p_awaiter) { - StringName signal = p_signal ? *p_signal : StringName(); - return (int32_t)gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter); -} - -MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) { - List<PropertyInfo> property_list; - p_ptr->get_property_list(&property_list); - - MonoArray *result = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), property_list.size()); - - int i = 0; - for (const PropertyInfo &E : property_list) { - MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E.name); - mono_array_setref(result, i, boxed); - i++; - } - - return result; -} - -MonoBoolean godot_icall_DynamicGodotObject_InvokeMember(Object *p_ptr, MonoString *p_name, MonoArray *p_args, MonoObject **r_result) { - String name = GDMonoMarshal::mono_string_to_godot(p_name); - - int argc = mono_array_length(p_args); - - ArgumentsVector<Variant> arg_store(argc); - ArgumentsVector<const Variant *> args(argc); - - for (int i = 0; i < argc; i++) { - MonoObject *elem = mono_array_get(p_args, MonoObject *, i); - arg_store.set(i, GDMonoMarshal::mono_object_to_variant(elem)); - args.set(i, &arg_store.get(i)); - } - - Callable::CallError error; - Variant result = p_ptr->callp(StringName(name), args.ptr(), argc, error); - - *r_result = GDMonoMarshal::variant_to_mono_object(result); - - return error.error == Callable::CallError::CALL_OK; -} - -MonoBoolean godot_icall_DynamicGodotObject_GetMember(Object *p_ptr, MonoString *p_name, MonoObject **r_result) { - String name = GDMonoMarshal::mono_string_to_godot(p_name); - - bool valid; - Variant value = p_ptr->get(StringName(name), &valid); - - if (valid) { - *r_result = GDMonoMarshal::variant_to_mono_object(value); - } - - return valid; -} - -MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *p_name, MonoObject *p_value) { - String name = GDMonoMarshal::mono_string_to_godot(p_name); - Variant value = GDMonoMarshal::mono_object_to_variant(p_value); - - bool valid; - p_ptr->set(StringName(name), value, &valid); - - return valid; -} - -MonoString *godot_icall_Object_ToString(Object *p_ptr) { -#ifdef DEBUG_ENABLED - // Cannot happen in C#; would get an ObjectDisposedException instead. - CRASH_COND(p_ptr == nullptr); -#endif - // 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); -} - -void godot_register_object_icalls() { - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Ctor", godot_icall_Object_Ctor); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Disposed", godot_icall_Object_Disposed); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_RefCounted_Disposed", godot_icall_RefCounted_Disposed); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ConnectEventSignals", godot_icall_Object_ConnectEventSignals); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", godot_icall_Object_ClassDB_get_method); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ToString", godot_icall_Object_ToString); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_weakref", godot_icall_Object_weakref); - GDMonoUtils::add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", godot_icall_SignalAwaiter_connect); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMemberList", godot_icall_DynamicGodotObject_SetMemberList); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_InvokeMember", godot_icall_DynamicGodotObject_InvokeMember); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_GetMember", godot_icall_DynamicGodotObject_GetMember); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMember", godot_icall_DynamicGodotObject_SetMember); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/callable_glue.cpp b/modules/mono/glue/callable_glue.cpp deleted file mode 100644 index 521dc3dff7..0000000000 --- a/modules/mono/glue/callable_glue.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************/ -/* callable_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "../mono_gd/gd_mono_marshal.h" -#include "arguments_vector.h" - -MonoObject *godot_icall_Callable_Call(GDMonoMarshal::M_Callable *p_callable, MonoArray *p_args) { - Callable callable = GDMonoMarshal::managed_to_callable(*p_callable); - - int argc = mono_array_length(p_args); - - ArgumentsVector<Variant> arg_store(argc); - ArgumentsVector<const Variant *> args(argc); - - for (int i = 0; i < argc; i++) { - MonoObject *elem = mono_array_get(p_args, MonoObject *, i); - arg_store.set(i, GDMonoMarshal::mono_object_to_variant(elem)); - args.set(i, &arg_store.get(i)); - } - - Variant result; - Callable::CallError error; - callable.callp(args.ptr(), argc, result, error); - - return GDMonoMarshal::variant_to_mono_object(result); -} - -void godot_icall_Callable_CallDeferred(GDMonoMarshal::M_Callable *p_callable, MonoArray *p_args) { - Callable callable = GDMonoMarshal::managed_to_callable(*p_callable); - - int argc = mono_array_length(p_args); - - ArgumentsVector<Variant> arg_store(argc); - ArgumentsVector<const Variant *> args(argc); - - for (int i = 0; i < argc; i++) { - MonoObject *elem = mono_array_get(p_args, MonoObject *, i); - arg_store.set(i, GDMonoMarshal::mono_object_to_variant(elem)); - args.set(i, &arg_store.get(i)); - } - - callable.call_deferredp(args.ptr(), argc); -} - -void godot_register_callable_icalls() { - GDMonoUtils::add_internal_call("Godot.Callable::godot_icall_Callable_Call", godot_icall_Callable_Call); - GDMonoUtils::add_internal_call("Godot.Callable::godot_icall_Callable_CallDeferred", godot_icall_Callable_CallDeferred); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp deleted file mode 100644 index 8a9f30459c..0000000000 --- a/modules/mono/glue/collections_glue.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/*************************************************************************/ -/* collections_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include <mono/metadata/exception.h> - -#include "core/variant/array.h" - -#include "../mono_gd/gd_mono_cache.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" - -Array *godot_icall_Array_Ctor() { - return memnew(Array); -} - -void godot_icall_Array_Dtor(Array *ptr) { - memdelete(ptr); -} - -MonoObject *godot_icall_Array_At(Array *ptr, int32_t index) { - if (index < 0 || index >= ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return nullptr; - } - return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index)); -} - -MonoObject *godot_icall_Array_At_Generic(Array *ptr, int32_t 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 nullptr; - } - return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index), ManagedType(type_encoding, type_class)); -} - -void godot_icall_Array_SetAt(Array *ptr, int32_t index, MonoObject *value) { - if (index < 0 || index >= ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return; - } - ptr->operator[](index) = GDMonoMarshal::mono_object_to_variant(value); -} - -int32_t godot_icall_Array_Count(Array *ptr) { - return ptr->size(); -} - -int32_t godot_icall_Array_Add(Array *ptr, MonoObject *item) { - ptr->append(GDMonoMarshal::mono_object_to_variant(item)); - return ptr->size(); -} - -void godot_icall_Array_Clear(Array *ptr) { - ptr->clear(); -} - -MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item) { - return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1; -} - -void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int32_t array_index) { - unsigned int count = ptr->size(); - - if (mono_array_length(array) < (array_index + count)) { - MonoException *exc = mono_get_exception_argument("", "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); - GDMonoUtils::set_pending_exception(exc); - return; - } - - for (unsigned int i = 0; i < count; i++) { - MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(ptr->operator[](i)); - mono_array_setref(array, array_index, boxed); - array_index++; - } -} - -Array *godot_icall_Array_Ctor_MonoArray(MonoArray *mono_array) { - Array *godot_array = memnew(Array); - unsigned int count = mono_array_length(mono_array); - godot_array->resize(count); - for (unsigned int i = 0; i < count; i++) { - MonoObject *item = mono_array_get(mono_array, MonoObject *, i); - godot_icall_Array_SetAt(godot_array, i, item); - } - return godot_array; -} - -Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep) { - return memnew(Array(ptr->duplicate(deep))); -} - -Array *godot_icall_Array_Concatenate(Array *left, Array *right) { - int count = left->size() + right->size(); - Array *new_array = memnew(Array(left->duplicate(false))); - new_array->resize(count); - for (unsigned int i = 0; i < (unsigned int)right->size(); i++) { - new_array->operator[](i + left->size()) = right->operator[](i); - } - return new_array; -} - -int32_t godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { - return ptr->find(GDMonoMarshal::mono_object_to_variant(item)); -} - -void godot_icall_Array_Insert(Array *ptr, int32_t index, MonoObject *item) { - if (index < 0 || index > ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return; - } - ptr->insert(index, GDMonoMarshal::mono_object_to_variant(item)); -} - -MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) { - int idx = ptr->find(GDMonoMarshal::mono_object_to_variant(item)); - if (idx >= 0) { - ptr->remove_at(idx); - return true; - } - return false; -} - -void godot_icall_Array_RemoveAt(Array *ptr, int32_t index) { - if (index < 0 || index >= ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return; - } - ptr->remove_at(index); -} - -int32_t godot_icall_Array_Resize(Array *ptr, int32_t new_size) { - return (int32_t)ptr->resize(new_size); -} - -void godot_icall_Array_Shuffle(Array *ptr) { - ptr->shuffle(); -} - -void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) { - MonoType *elem_type = mono_reflection_type_get_type(refltype); - - *type_encoding = mono_type_get_type(elem_type); - MonoClass *type_class_raw = mono_class_from_mono_type(elem_type); - *type_class = GDMono::get_singleton()->get_class(type_class_raw); -} - -MonoString *godot_icall_Array_ToString(Array *ptr) { - return GDMonoMarshal::mono_string_from_godot(Variant(*ptr).operator String()); -} - -Dictionary *godot_icall_Dictionary_Ctor() { - return memnew(Dictionary); -} - -void godot_icall_Dictionary_Dtor(Dictionary *ptr) { - memdelete(ptr); -} - -MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) { - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - 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 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 == 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 nullptr; - } - return GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); -} - -void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value) { - ptr->operator[](GDMonoMarshal::mono_object_to_variant(key)) = GDMonoMarshal::mono_object_to_variant(value); -} - -Array *godot_icall_Dictionary_Keys(Dictionary *ptr) { - return memnew(Array(ptr->keys())); -} - -Array *godot_icall_Dictionary_Values(Dictionary *ptr) { - return memnew(Array(ptr->values())); -} - -int32_t godot_icall_Dictionary_Count(Dictionary *ptr) { - return ptr->size(); -} - -int32_t godot_icall_Dictionary_KeyValuePairs(Dictionary *ptr, Array **keys, Array **values) { - *keys = godot_icall_Dictionary_Keys(ptr); - *values = godot_icall_Dictionary_Values(ptr); - return godot_icall_Dictionary_Count(ptr); -} - -void godot_icall_Dictionary_KeyValuePairAt(Dictionary *ptr, int index, MonoObject **key, MonoObject **value) { - *key = GDMonoMarshal::variant_to_mono_object(ptr->get_key_at_index(index)); - *value = GDMonoMarshal::variant_to_mono_object(ptr->get_value_at_index(index)); -} - -void godot_icall_Dictionary_KeyValuePairAt_Generic(Dictionary *ptr, int index, MonoObject **key, MonoObject **value, uint32_t value_type_encoding, GDMonoClass *value_type_class) { - ManagedType type(value_type_encoding, value_type_class); - *key = GDMonoMarshal::variant_to_mono_object(ptr->get_key_at_index(index)); - *value = GDMonoMarshal::variant_to_mono_object(ptr->get_value_at_index(index), type); -} - -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 != nullptr) { - GDMonoUtils::set_pending_exception(mono_get_exception_argument("key", "An element with the same key already exists")); - return; - } - ptr->operator[](varKey) = GDMonoMarshal::mono_object_to_variant(value); -} - -void godot_icall_Dictionary_Clear(Dictionary *ptr) { - ptr->clear(); -} - -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 != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value); -} - -MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) { - return ptr->has(GDMonoMarshal::mono_object_to_variant(key)); -} - -Dictionary *godot_icall_Dictionary_Duplicate(Dictionary *ptr, MonoBoolean deep) { - return memnew(Dictionary(ptr->duplicate(deep))); -} - -MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) { - return ptr->erase(GDMonoMarshal::mono_object_to_variant(key)); -} - -MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value) { - Variant varKey = GDMonoMarshal::mono_object_to_variant(key); - - // no dupes - Variant *ret = ptr->getptr(varKey); - if (ret != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value)) { - ptr->erase(varKey); - return true; - } - - return false; -} - -MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) { - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == nullptr) { - *value = nullptr; - return false; - } - *value = GDMonoMarshal::variant_to_mono_object(ret); - return true; -} - -MonoBoolean godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class) { - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == nullptr) { - *value = nullptr; - return false; - } - *value = GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); - return true; -} - -void godot_icall_Dictionary_Generic_GetValueTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) { - MonoType *value_type = mono_reflection_type_get_type(refltype); - - *type_encoding = mono_type_get_type(value_type); - MonoClass *type_class_raw = mono_class_from_mono_type(value_type); - *type_class = GDMono::get_singleton()->get_class(type_class_raw); -} - -MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr) { - return GDMonoMarshal::mono_string_from_godot(Variant(*ptr).operator String()); -} - -void godot_register_collections_icalls() { - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", godot_icall_Array_Ctor); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor_MonoArray", godot_icall_Array_Ctor_MonoArray); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", godot_icall_Array_Dtor); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At", godot_icall_Array_At); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", godot_icall_Array_At_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", godot_icall_Array_SetAt); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", godot_icall_Array_Count); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", godot_icall_Array_Add); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", godot_icall_Array_Clear); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Concatenate", godot_icall_Array_Concatenate); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", godot_icall_Array_Contains); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", godot_icall_Array_CopyTo); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", godot_icall_Array_Duplicate); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", godot_icall_Array_IndexOf); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", godot_icall_Array_Insert); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", godot_icall_Array_Remove); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", godot_icall_Array_RemoveAt); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Resize", godot_icall_Array_Resize); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Shuffle", godot_icall_Array_Shuffle); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", godot_icall_Array_Generic_GetElementTypeInfo); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_ToString", godot_icall_Array_ToString); - - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", godot_icall_Dictionary_Ctor); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", godot_icall_Dictionary_Dtor); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", godot_icall_Dictionary_GetValue); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", godot_icall_Dictionary_GetValue_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", godot_icall_Dictionary_SetValue); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", godot_icall_Dictionary_Keys); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", godot_icall_Dictionary_Values); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", godot_icall_Dictionary_Count); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairs", godot_icall_Dictionary_KeyValuePairs); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairAt", godot_icall_Dictionary_KeyValuePairAt); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairAt_Generic", godot_icall_Dictionary_KeyValuePairAt_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", godot_icall_Dictionary_Add); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", godot_icall_Dictionary_Clear); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", godot_icall_Dictionary_Contains); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", godot_icall_Dictionary_ContainsKey); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", godot_icall_Dictionary_Duplicate); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", godot_icall_Dictionary_RemoveKey); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", godot_icall_Dictionary_Remove); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", godot_icall_Dictionary_TryGetValue); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", godot_icall_Dictionary_TryGetValue_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", godot_icall_Dictionary_Generic_GetValueTypeInfo); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ToString", godot_icall_Dictionary_ToString); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp deleted file mode 100644 index 8b1c2b729e..0000000000 --- a/modules/mono/glue/gd_glue.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/*************************************************************************/ -/* gd_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/io/marshalls.h" -#include "core/os/os.h" -#include "core/string/ustring.h" -#include "core/variant/array.h" -#include "core/variant/variant.h" -#include "core/variant/variant_parser.h" - -#include "../mono_gd/gd_mono_cache.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" - -MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects) { - Variant ret; - PackedByteArray varr = GDMonoMarshal::mono_array_to_PackedByteArray(p_bytes); - 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."); - } - return GDMonoMarshal::variant_to_mono_object(ret); -} - -MonoObject *godot_icall_GD_convert(MonoObject *p_what, int32_t p_type) { - Variant what = GDMonoMarshal::mono_object_to_variant(p_what); - const Variant *args[1] = { &what }; - Callable::CallError ce; - Variant ret; - Variant::construct(Variant::Type(p_type), ret, args, 1, ce); - ERR_FAIL_COND_V(ce.error != Callable::CallError::CALL_OK, nullptr); - return GDMonoMarshal::variant_to_mono_object(ret); -} - -int godot_icall_GD_hash(MonoObject *p_var) { - return GDMonoMarshal::mono_object_to_variant(p_var).hash(); -} - -MonoObject *godot_icall_GD_instance_from_id(uint64_t p_instance_id) { - return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(ObjectID(p_instance_id))); -} - -void godot_icall_GD_print(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - print_line(str); -} - -void godot_icall_GD_print_rich(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - print_line_rich(str); -} - -void godot_icall_GD_printerr(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - print_error(str); -} - -void godot_icall_GD_printraw(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - OS::get_singleton()->print("%s", str.utf8().get_data()); -} - -void godot_icall_GD_prints(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - if (i) { - str += " "; - } - - str += elem_str; - } - - print_line(str); -} - -void godot_icall_GD_printt(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - if (i) { - str += "\t"; - } - - str += elem_str; - } - - print_line(str); -} - -void godot_icall_GD_randomize() { - Math::randomize(); -} - -uint32_t godot_icall_GD_randi() { - return Math::rand(); -} - -float godot_icall_GD_randf() { - return Math::randf(); -} - -int32_t godot_icall_GD_randi_range(int32_t from, int32_t to) { - return Math::random(from, to); -} - -double godot_icall_GD_randf_range(double from, double to) { - return Math::random(from, to); -} - -double godot_icall_GD_randfn(double mean, double deviation) { - return Math::randfn(mean, deviation); -} - -uint32_t godot_icall_GD_rand_seed(uint64_t seed, uint64_t *newSeed) { - uint32_t ret = Math::rand_from_seed(&seed); - *newSeed = seed; - return ret; -} - -void godot_icall_GD_seed(uint64_t p_seed) { - Math::seed(p_seed); -} - -MonoString *godot_icall_GD_str(MonoArray *p_what) { - String str; - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - - for (int i = 0; i < what.size(); i++) { - String os = what[i].operator String(); - - if (i == 0) { - str = os; - } else { - str += os; - } - } - - return GDMonoMarshal::mono_string_from_godot(str); -} - -MonoObject *godot_icall_GD_str2var(MonoString *p_str) { - Variant ret; - - VariantParser::StreamString ss; - ss.s = GDMonoMarshal::mono_string_to_godot(p_str); - - String errs; - int line; - Error err = VariantParser::parse(&ss, ret, errs, line); - if (err != OK) { - String err_str = "Parse error at line " + itos(line) + ": " + errs + "."; - ERR_PRINT(err_str); - ret = err_str; - } - - return GDMonoMarshal::variant_to_mono_object(ret); -} - -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) { - ERR_PRINT(GDMonoMarshal::mono_string_to_godot(p_str)); -} - -void godot_icall_GD_pushwarning(MonoString *p_str) { - WARN_PRINT(GDMonoMarshal::mono_string_to_godot(p_str)); -} - -MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_objects) { - Variant var = GDMonoMarshal::mono_object_to_variant(p_var); - - PackedByteArray barr; - int len; - 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); - - return GDMonoMarshal::PackedByteArray_to_mono_array(barr); -} - -MonoString *godot_icall_GD_var2str(MonoObject *p_var) { - String vars; - VariantWriter::write_to_string(GDMonoMarshal::mono_object_to_variant(p_var), vars); - 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(); -} - -void godot_register_gd_icalls() { - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_bytes2var", godot_icall_GD_bytes2var); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_convert", godot_icall_GD_convert); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_hash", godot_icall_GD_hash); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", godot_icall_GD_instance_from_id); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pusherror", godot_icall_GD_pusherror); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pushwarning", godot_icall_GD_pushwarning); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print", godot_icall_GD_print); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print_rich", godot_icall_GD_print_rich); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printerr", godot_icall_GD_printerr); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printraw", godot_icall_GD_printraw); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_prints", godot_icall_GD_prints); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printt", godot_icall_GD_printt); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randomize", godot_icall_GD_randomize); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi", godot_icall_GD_randi); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf", godot_icall_GD_randf); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi_range", godot_icall_GD_randi_range); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf_range", godot_icall_GD_randf_range); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randfn", godot_icall_GD_randfn); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_rand_seed", godot_icall_GD_rand_seed); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_seed", godot_icall_GD_seed); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str", godot_icall_GD_str); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str2var", godot_icall_GD_str2var); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_type_exists", godot_icall_GD_type_exists); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2bytes", godot_icall_GD_var2bytes); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2str", godot_icall_GD_var2str); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_TypeToVariantType", godot_icall_TypeToVariantType); - - // Dispatcher - GDMonoUtils::add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", godot_icall_DefaultGodotTaskScheduler); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h deleted file mode 100644 index f9ad1a9893..0000000000 --- a/modules/mono/glue/glue_header.h +++ /dev/null @@ -1,91 +0,0 @@ -/*************************************************************************/ -/* glue_header.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 GLUE_HEADER_H -#define GLUE_HEADER_H - -#ifdef MONO_GLUE_ENABLED - -#include "../mono_gd/gd_mono_marshal.h" - -void godot_register_collections_icalls(); -void godot_register_gd_icalls(); -void godot_register_string_name_icalls(); -void godot_register_nodepath_icalls(); -void godot_register_callable_icalls(); -void godot_register_object_icalls(); -void godot_register_rid_icalls(); -void godot_register_string_icalls(); -void godot_register_scene_tree_icalls(); - -/** - * Registers internal calls that were not generated. This function is called - * from the generated GodotSharpBindings::register_generated_icalls() function. - */ -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_callable_icalls(); - godot_register_object_icalls(); - godot_register_rid_icalls(); - godot_register_string_icalls(); - godot_register_scene_tree_icalls(); -} - -// Used by the generated glue - -#include "core/config/engine.h" -#include "core/object/class_db.h" -#include "core/object/method_bind.h" -#include "core/object/ref_counted.h" -#include "core/string/node_path.h" -#include "core/string/ustring.h" -#include "core/typedefs.h" -#include "core/variant/array.h" -#include "core/variant/dictionary.h" - -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_internals.h" -#include "../mono_gd/gd_mono_utils.h" - -#define GODOTSHARP_INSTANCE_OBJECT(m_instance, m_type) \ - static ClassDB::ClassInfo *ci = nullptr; \ - if (!ci) { \ - ci = ClassDB::classes.getptr(m_type); \ - } \ - Object *m_instance = ci->creation_func(); - -#include "arguments_vector.h" - -#endif // MONO_GLUE_ENABLED - -#endif // GLUE_HEADER_H diff --git a/modules/mono/glue/nodepath_glue.cpp b/modules/mono/glue/nodepath_glue.cpp deleted file mode 100644 index 16e1509eb0..0000000000 --- a/modules/mono/glue/nodepath_glue.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/*************************************************************************/ -/* nodepath_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/string/node_path.h" -#include "core/string/ustring.h" - -#include "../mono_gd/gd_mono_marshal.h" - -NodePath *godot_icall_NodePath_Ctor(MonoString *p_path) { - return memnew(NodePath(GDMonoMarshal::mono_string_to_godot(p_path))); -} - -void godot_icall_NodePath_Dtor(NodePath *p_ptr) { - ERR_FAIL_NULL(p_ptr); - memdelete(p_ptr); -} - -MonoString *godot_icall_NodePath_operator_String(NodePath *p_np) { - return GDMonoMarshal::mono_string_from_godot(p_np->operator String()); -} - -MonoBoolean godot_icall_NodePath_is_absolute(NodePath *p_ptr) { - return (MonoBoolean)p_ptr->is_absolute(); -} - -int32_t godot_icall_NodePath_get_name_count(NodePath *p_ptr) { - return p_ptr->get_name_count(); -} - -MonoString *godot_icall_NodePath_get_name(NodePath *p_ptr, uint32_t p_idx) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_name(p_idx)); -} - -int32_t godot_icall_NodePath_get_subname_count(NodePath *p_ptr) { - return p_ptr->get_subname_count(); -} - -MonoString *godot_icall_NodePath_get_subname(NodePath *p_ptr, uint32_t p_idx) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_subname(p_idx)); -} - -MonoString *godot_icall_NodePath_get_concatenated_names(NodePath *p_ptr) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_concatenated_names()); -} - -MonoString *godot_icall_NodePath_get_concatenated_subnames(NodePath *p_ptr) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_concatenated_subnames()); -} - -NodePath *godot_icall_NodePath_get_as_property_path(NodePath *p_ptr) { - return memnew(NodePath(p_ptr->get_as_property_path())); -} - -MonoBoolean godot_icall_NodePath_is_empty(NodePath *p_ptr) { - return (MonoBoolean)p_ptr->is_empty(); -} - -void godot_register_nodepath_icalls() { - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Ctor", godot_icall_NodePath_Ctor); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", godot_icall_NodePath_Dtor); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", godot_icall_NodePath_operator_String); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", godot_icall_NodePath_get_as_property_path); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_names", godot_icall_NodePath_get_concatenated_names); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", godot_icall_NodePath_get_concatenated_subnames); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", godot_icall_NodePath_get_name); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", godot_icall_NodePath_get_name_count); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname", godot_icall_NodePath_get_subname); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname_count", godot_icall_NodePath_get_subname_count); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_absolute", godot_icall_NodePath_is_absolute); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_empty", godot_icall_NodePath_is_empty); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp deleted file mode 100644 index 3e09564539..0000000000 --- a/modules/mono/glue/rid_glue.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/*************************************************************************/ -/* rid_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/io/resource.h" -#include "core/object/class_db.h" -#include "core/templates/rid.h" - -#include "../mono_gd/gd_mono_marshal.h" - -RID *godot_icall_RID_Ctor(Object *p_from) { - Resource *res_from = Object::cast_to<Resource>(p_from); - - if (res_from) { - return memnew(RID(res_from->get_rid())); - } - - return memnew(RID); -} - -void godot_icall_RID_Dtor(RID *p_ptr) { - ERR_FAIL_NULL(p_ptr); - memdelete(p_ptr); -} - -uint32_t godot_icall_RID_get_id(RID *p_ptr) { - return p_ptr->get_id(); -} - -void godot_register_rid_icalls() { - GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Ctor", godot_icall_RID_Ctor); - GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Dtor", godot_icall_RID_Dtor); - GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_get_id", godot_icall_RID_get_id); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp new file mode 100644 index 0000000000..13d4395a64 --- /dev/null +++ b/modules/mono/glue/runtime_interop.cpp @@ -0,0 +1,1513 @@ +/*************************************************************************/ +/* runtime_interop.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "runtime_interop.h" + +#include "core/config/engine.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" +#include "core/io/marshalls.h" +#include "core/object/class_db.h" +#include "core/object/method_bind.h" +#include "core/os/os.h" +#include "core/string/string_name.h" + +#include "../interop_types.h" + +#include "modules/mono/csharp_script.h" +#include "modules/mono/managed_callable.h" +#include "modules/mono/mono_gd/gd_mono_cache.h" +#include "modules/mono/signal_awaiter_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// For ArrayPrivate and DictionaryPrivate +static_assert(sizeof(SafeRefCount) == sizeof(uint32_t)); + +typedef Object *(*godotsharp_class_creation_func)(); + +MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, const StringName *p_methodname) { + return ClassDB::get_method(*p_classname, *p_methodname); +} + +godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) { + ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(*p_classname); + if (class_info) { + return class_info->creation_func; + } + return nullptr; +} + +Object *godotsharp_engine_get_singleton(const String *p_name) { + return Engine::get_singleton()->get_singleton_object(*p_name); +} + +int32_t godotsharp_stack_info_vector_resize( + Vector<ScriptLanguage::StackInfo> *p_stack_info_vector, int p_size) { + return (int32_t)p_stack_info_vector->resize(p_size); +} + +void godotsharp_stack_info_vector_destroy( + Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { + p_stack_info_vector->~Vector(); +} + +void godotsharp_internal_script_debugger_send_error(const String *p_func, + const String *p_file, int32_t p_line, const String *p_err, const String *p_descr, + bool p_warning, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { + EngineDebugger::get_script_debugger()->send_error(*p_func, *p_file, p_line, *p_err, *p_descr, + true, p_warning ? ERR_HANDLER_WARNING : ERR_HANDLER_ERROR, *p_stack_info_vector); +} + +bool godotsharp_internal_script_debugger_is_active() { + return EngineDebugger::is_active(); +} + +GCHandleIntPtr godotsharp_internal_object_get_associated_gchandle(Object *p_ptr) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == nullptr); +#endif + + if (p_ptr->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + return cs_instance->get_gchandle_intptr(); + } + return { nullptr }; + } + } + + void *data = CSharpLanguage::get_existing_instance_binding(p_ptr); + + if (data) { + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); + if (script_binding.inited) { + MonoGCHandleData &gchandle = script_binding.gchandle; + return !gchandle.is_released() ? gchandle.get_intptr() : GCHandleIntPtr{ nullptr }; + } + } + + return { nullptr }; +} + +void godotsharp_internal_object_disposed(Object *p_ptr, GCHandleIntPtr p_gchandle_to_free) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == nullptr); +#endif + + if (p_ptr->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + cs_instance->mono_object_disposed(p_gchandle_to_free); + p_ptr->set_script_instance(nullptr); + } + return; + } + } + + void *data = CSharpLanguage::get_existing_instance_binding(p_ptr); + + if (data) { + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); + if (script_binding.inited) { + if (!script_binding.gchandle.is_released()) { + CSharpLanguage::release_binding_gchandle_thread_safe(p_gchandle_to_free, script_binding); + } + } + } +} + +void godotsharp_internal_refcounted_disposed(Object *p_ptr, GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == nullptr); + // This is only called with RefCounted derived classes + CRASH_COND(!Object::cast_to<RefCounted>(p_ptr)); +#endif + + RefCounted *rc = static_cast<RefCounted *>(p_ptr); + + if (rc->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(rc->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + bool delete_owner; + bool remove_script_instance; + + cs_instance->mono_object_disposed_baseref(p_gchandle_to_free, p_is_finalizer, + delete_owner, remove_script_instance); + + if (delete_owner) { + memdelete(rc); + } else if (remove_script_instance) { + rc->set_script_instance(nullptr); + } + } + return; + } + } + + // Unsafe refcount decrement. The managed instance also counts as a reference. + // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + CSharpLanguage::get_singleton()->pre_unsafe_unreference(rc); + if (rc->unreference()) { + memdelete(rc); + } else { + void *data = CSharpLanguage::get_existing_instance_binding(rc); + + if (data) { + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); + if (script_binding.inited) { + if (!script_binding.gchandle.is_released()) { + CSharpLanguage::release_binding_gchandle_thread_safe(p_gchandle_to_free, script_binding); + } + } + } + } +} + +int32_t godotsharp_internal_signal_awaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, GCHandleIntPtr p_awaiter_handle_ptr) { + StringName signal = p_signal ? *p_signal : StringName(); + return (int32_t)gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter_handle_ptr); +} + +GCHandleIntPtr godotsharp_internal_unmanaged_get_script_instance_managed(Object *p_unmanaged, bool *r_has_cs_script_instance) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_unmanaged); + CRASH_COND(!r_has_cs_script_instance); +#endif + + if (p_unmanaged->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_unmanaged->get_script_instance()); + + if (cs_instance) { + *r_has_cs_script_instance = true; + return cs_instance->get_gchandle_intptr(); + } + } + + *r_has_cs_script_instance = false; + return { nullptr }; +} + +GCHandleIntPtr godotsharp_internal_unmanaged_get_instance_binding_managed(Object *p_unmanaged) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_unmanaged); +#endif + + void *data = CSharpLanguage::get_instance_binding(p_unmanaged); + ERR_FAIL_NULL_V(data, { nullptr }); + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value(); + ERR_FAIL_COND_V(!script_binding.inited, { nullptr }); + + return script_binding.gchandle.get_intptr(); +} + +GCHandleIntPtr godotsharp_internal_unmanaged_instance_binding_create_managed(Object *p_unmanaged, GCHandleIntPtr p_old_gchandle) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_unmanaged); +#endif + + void *data = CSharpLanguage::get_instance_binding(p_unmanaged); + ERR_FAIL_NULL_V(data, { nullptr }); + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value(); + ERR_FAIL_COND_V(!script_binding.inited, { nullptr }); + + MonoGCHandleData &gchandle = script_binding.gchandle; + + // TODO: Possible data race? + CRASH_COND(gchandle.get_intptr().value != p_old_gchandle.value); + + CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); + script_binding.inited = false; + + // Create a new one + +#ifdef DEBUG_ENABLED + CRASH_COND(script_binding.type_name == StringName()); +#endif + + bool parent_is_object_class = ClassDB::is_parent_class(p_unmanaged->get_class_name(), script_binding.type_name); + ERR_FAIL_COND_V_MSG(!parent_is_object_class, { nullptr }, + "Type inherits from native type '" + script_binding.type_name + "', so it can't be instantiated in object of type: '" + p_unmanaged->get_class() + "'."); + + GCHandleIntPtr strong_gchandle = + GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding( + &script_binding.type_name, p_unmanaged); + + ERR_FAIL_NULL_V(strong_gchandle.value, { nullptr }); + + gchandle = MonoGCHandleData(strong_gchandle, gdmono::GCHandleType::STRONG_HANDLE); + script_binding.inited = true; + + // Tie managed to unmanaged + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); + + if (rc) { + // Unsafe refcount increment. The managed instance also counts as a reference. + // This way if the unmanaged world has no references to our owner + // but the managed instance is alive, the refcount will be 1 instead of 0. + // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) + rc->reference(); + CSharpLanguage::get_singleton()->post_unsafe_reference(rc); + } + + return gchandle.get_intptr(); +} + +void godotsharp_internal_tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted) { + CSharpLanguage::tie_native_managed_to_unmanaged(p_gchandle_intptr, p_unmanaged, p_native_name, p_ref_counted); +} + +void godotsharp_internal_tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted) { + CSharpLanguage::tie_user_managed_to_unmanaged(p_gchandle_intptr, p_unmanaged, p_script, p_ref_counted); +} + +void godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged) { + CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(p_gchandle_intptr, p_unmanaged); +} + +void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) { + memnew_placement(r_dest, Ref<CSharpScript>(memnew(CSharpScript))); +} + +bool godotsharp_internal_script_load(const String *p_path, Ref<CSharpScript> *r_dest) { + Ref<Resource> res = ResourceLoader::load(*p_path); + if (res.is_valid()) { + memnew_placement(r_dest, Ref<CSharpScript>(res)); + return true; + } else { + memnew_placement(r_dest, Ref<CSharpScript>()); + return false; + } +} + +void godotsharp_internal_reload_registered_script(CSharpScript *p_script) { + CRASH_COND(!p_script); + CSharpScript::reload_registered_script(Ref<CSharpScript>(p_script)); +} + +void godotsharp_array_filter_godot_objects_by_native(StringName *p_native_name, const Array *p_input, Array *r_output) { + memnew_placement(r_output, Array); + + for (int i = 0; i < p_input->size(); ++i) { + if (ClassDB::is_parent_class(((Object *)(*p_input)[i])->get_class(), *p_native_name)) { + r_output->push_back(p_input[i]); + } + } +} + +void godotsharp_array_filter_godot_objects_by_non_native(const Array *p_input, Array *r_output) { + memnew_placement(r_output, Array); + + for (int i = 0; i < p_input->size(); ++i) { + CSharpInstance *si = CAST_CSHARP_INSTANCE(((Object *)(*p_input)[i])->get_script_instance()); + + if (si != nullptr) { + r_output->push_back(p_input[i]); + } + } +} + +void godotsharp_ref_new_from_ref_counted_ptr(Ref<RefCounted> *r_dest, RefCounted *p_ref_counted_ptr) { + memnew_placement(r_dest, Ref<RefCounted>(p_ref_counted_ptr)); +} + +void godotsharp_ref_destroy(Ref<RefCounted> *p_instance) { + p_instance->~Ref(); +} + +void godotsharp_string_name_new_from_string(StringName *r_dest, const String *p_name) { + memnew_placement(r_dest, StringName(*p_name)); +} + +void godotsharp_node_path_new_from_string(NodePath *r_dest, const String *p_name) { + memnew_placement(r_dest, NodePath(*p_name)); +} + +void godotsharp_string_name_as_string(String *r_dest, const StringName *p_name) { + memnew_placement(r_dest, String(p_name->operator String())); +} + +void godotsharp_node_path_as_string(String *r_dest, const NodePath *p_np) { + memnew_placement(r_dest, String(p_np->operator String())); +} + +godot_packed_array godotsharp_packed_byte_array_new_mem_copy(const uint8_t *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedByteArray); + PackedByteArray *array = reinterpret_cast<PackedByteArray *>(&ret); + array->resize(p_length); + uint8_t *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(uint8_t)); + return ret; +} + +godot_packed_array godotsharp_packed_int32_array_new_mem_copy(const int32_t *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedInt32Array); + PackedInt32Array *array = reinterpret_cast<PackedInt32Array *>(&ret); + array->resize(p_length); + int32_t *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(int32_t)); + return ret; +} + +godot_packed_array godotsharp_packed_int64_array_new_mem_copy(const int64_t *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedInt64Array); + PackedInt64Array *array = reinterpret_cast<PackedInt64Array *>(&ret); + array->resize(p_length); + int64_t *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(int64_t)); + return ret; +} + +godot_packed_array godotsharp_packed_float32_array_new_mem_copy(const float *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedFloat32Array); + PackedFloat32Array *array = reinterpret_cast<PackedFloat32Array *>(&ret); + array->resize(p_length); + float *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(float)); + return ret; +} + +godot_packed_array godotsharp_packed_float64_array_new_mem_copy(const double *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedFloat64Array); + PackedFloat64Array *array = reinterpret_cast<PackedFloat64Array *>(&ret); + array->resize(p_length); + double *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(double)); + return ret; +} + +godot_packed_array godotsharp_packed_vector2_array_new_mem_copy(const Vector2 *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedVector2Array); + PackedVector2Array *array = reinterpret_cast<PackedVector2Array *>(&ret); + array->resize(p_length); + Vector2 *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(Vector2)); + return ret; +} + +godot_packed_array godotsharp_packed_vector3_array_new_mem_copy(const Vector3 *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedVector3Array); + PackedVector3Array *array = reinterpret_cast<PackedVector3Array *>(&ret); + array->resize(p_length); + Vector3 *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(Vector3)); + return ret; +} + +godot_packed_array godotsharp_packed_color_array_new_mem_copy(const Color *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedColorArray); + PackedColorArray *array = reinterpret_cast<PackedColorArray *>(&ret); + array->resize(p_length); + Color *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(Color)); + return ret; +} + +void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String *p_element) { + r_dest->append(*p_element); +} + +void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, Callable *r_callable) { + // TODO: Use pooling for ManagedCallable instances. + CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle)); + memnew_placement(r_callable, Callable(managed_callable)); +} + +bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable, + GCHandleIntPtr *r_delegate_handle, Object **r_object, StringName *r_name) { + 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); + *r_delegate_handle = managed_callable->get_delegate(); + *r_object = nullptr; + memnew_placement(r_name, StringName()); + return true; + } else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) { + SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom); + *r_delegate_handle = { nullptr }; + *r_object = ObjectDB::get_instance(signal_awaiter_callable->get_object()); + memnew_placement(r_name, StringName(signal_awaiter_callable->get_signal())); + return true; + } else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) { + EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom); + *r_delegate_handle = { nullptr }; + *r_object = ObjectDB::get_instance(event_signal_callable->get_object()); + memnew_placement(r_name, StringName(event_signal_callable->get_signal())); + return true; + } + + // Some other CallableCustom. We only support ManagedCallable. + *r_delegate_handle = { nullptr }; + *r_object = nullptr; + memnew_placement(r_name, StringName()); + return false; + } else { + *r_delegate_handle = { nullptr }; + *r_object = ObjectDB::get_instance(p_callable->get_object_id()); + memnew_placement(r_name, StringName(p_callable->get_method())); + return true; + } +} + +godot_variant godotsharp_callable_call(Callable *p_callable, const Variant **p_args, const int32_t p_arg_count, Callable::CallError *p_call_error) { + godot_variant ret; + memnew_placement(&ret, Variant); + + Variant *ret_val = (Variant *)&ret; + + p_callable->callp(p_args, p_arg_count, *ret_val, *p_call_error); + + return ret; +} + +void godotsharp_callable_call_deferred(Callable *p_callable, const Variant **p_args, const int32_t p_arg_count) { + p_callable->call_deferredp(p_args, p_arg_count); +} + +// GDNative functions + +// gdnative.h + +void godotsharp_method_bind_ptrcall(MethodBind *p_method_bind, Object *p_instance, const void **p_args, void *p_ret) { + p_method_bind->ptrcall(p_instance, p_args, p_ret); +} + +godot_variant godotsharp_method_bind_call(MethodBind *p_method_bind, Object *p_instance, const godot_variant **p_args, const int32_t p_arg_count, Callable::CallError *p_call_error) { + godot_variant ret; + memnew_placement(&ret, Variant()); + + Variant *ret_val = (Variant *)&ret; + + *ret_val = p_method_bind->call(p_instance, (const Variant **)p_args, p_arg_count, *p_call_error); + + return ret; +} + +// variant.h + +void godotsharp_variant_new_copy(godot_variant *r_dest, const Variant *p_src) { + memnew_placement(r_dest, Variant(*p_src)); +} + +void godotsharp_variant_new_string_name(godot_variant *r_dest, const StringName *p_s) { + memnew_placement(r_dest, Variant(*p_s)); +} + +void godotsharp_variant_new_node_path(godot_variant *r_dest, const NodePath *p_np) { + memnew_placement(r_dest, Variant(*p_np)); +} + +void godotsharp_variant_new_object(godot_variant *r_dest, const Object *p_obj) { + memnew_placement(r_dest, Variant(p_obj)); +} + +void godotsharp_variant_new_transform2d(godot_variant *r_dest, const Transform2D *p_t2d) { + memnew_placement(r_dest, Variant(*p_t2d)); +} + +void godotsharp_variant_new_vector4(godot_variant *r_dest, const Vector4 *p_vec4) { + memnew_placement(r_dest, Variant(*p_vec4)); +} + +void godotsharp_variant_new_vector4i(godot_variant *r_dest, const Vector4i *p_vec4i) { + memnew_placement(r_dest, Variant(*p_vec4i)); +} + +void godotsharp_variant_new_basis(godot_variant *r_dest, const Basis *p_basis) { + memnew_placement(r_dest, Variant(*p_basis)); +} + +void godotsharp_variant_new_transform3d(godot_variant *r_dest, const Transform3D *p_trans) { + memnew_placement(r_dest, Variant(*p_trans)); +} + +void godotsharp_variant_new_projection(godot_variant *r_dest, const Projection *p_proj) { + memnew_placement(r_dest, Variant(*p_proj)); +} + +void godotsharp_variant_new_aabb(godot_variant *r_dest, const AABB *p_aabb) { + memnew_placement(r_dest, Variant(*p_aabb)); +} + +void godotsharp_variant_new_dictionary(godot_variant *r_dest, const Dictionary *p_dict) { + memnew_placement(r_dest, Variant(*p_dict)); +} + +void godotsharp_variant_new_array(godot_variant *r_dest, const Array *p_arr) { + memnew_placement(r_dest, Variant(*p_arr)); +} + +void godotsharp_variant_new_packed_byte_array(godot_variant *r_dest, const PackedByteArray *p_pba) { + memnew_placement(r_dest, Variant(*p_pba)); +} + +void godotsharp_variant_new_packed_int32_array(godot_variant *r_dest, const PackedInt32Array *p_pia) { + memnew_placement(r_dest, Variant(*p_pia)); +} + +void godotsharp_variant_new_packed_int64_array(godot_variant *r_dest, const PackedInt64Array *p_pia) { + memnew_placement(r_dest, Variant(*p_pia)); +} + +void godotsharp_variant_new_packed_float32_array(godot_variant *r_dest, const PackedFloat32Array *p_pra) { + memnew_placement(r_dest, Variant(*p_pra)); +} + +void godotsharp_variant_new_packed_float64_array(godot_variant *r_dest, const PackedFloat64Array *p_pra) { + memnew_placement(r_dest, Variant(*p_pra)); +} + +void godotsharp_variant_new_packed_string_array(godot_variant *r_dest, const PackedStringArray *p_psa) { + memnew_placement(r_dest, Variant(*p_psa)); +} + +void godotsharp_variant_new_packed_vector2_array(godot_variant *r_dest, const PackedVector2Array *p_pv2a) { + memnew_placement(r_dest, Variant(*p_pv2a)); +} + +void godotsharp_variant_new_packed_vector3_array(godot_variant *r_dest, const PackedVector3Array *p_pv3a) { + memnew_placement(r_dest, Variant(*p_pv3a)); +} + +void godotsharp_variant_new_packed_color_array(godot_variant *r_dest, const PackedColorArray *p_pca) { + memnew_placement(r_dest, Variant(*p_pca)); +} + +bool godotsharp_variant_as_bool(const Variant *p_self) { + return p_self->operator bool(); +} + +int64_t godotsharp_variant_as_int(const Variant *p_self) { + return p_self->operator int64_t(); +} + +double godotsharp_variant_as_float(const Variant *p_self) { + return p_self->operator double(); +} + +godot_string godotsharp_variant_as_string(const Variant *p_self) { + godot_string raw_dest; + String *dest = (String *)&raw_dest; + memnew_placement(dest, String(p_self->operator String())); + return raw_dest; +} + +godot_vector2 godotsharp_variant_as_vector2(const Variant *p_self) { + godot_vector2 raw_dest; + Vector2 *dest = (Vector2 *)&raw_dest; + memnew_placement(dest, Vector2(p_self->operator Vector2())); + return raw_dest; +} + +godot_vector2i godotsharp_variant_as_vector2i(const Variant *p_self) { + godot_vector2i raw_dest; + Vector2i *dest = (Vector2i *)&raw_dest; + memnew_placement(dest, Vector2i(p_self->operator Vector2i())); + return raw_dest; +} + +godot_rect2 godotsharp_variant_as_rect2(const Variant *p_self) { + godot_rect2 raw_dest; + Rect2 *dest = (Rect2 *)&raw_dest; + memnew_placement(dest, Rect2(p_self->operator Rect2())); + return raw_dest; +} + +godot_rect2i godotsharp_variant_as_rect2i(const Variant *p_self) { + godot_rect2i raw_dest; + Rect2i *dest = (Rect2i *)&raw_dest; + memnew_placement(dest, Rect2i(p_self->operator Rect2i())); + return raw_dest; +} + +godot_vector3 godotsharp_variant_as_vector3(const Variant *p_self) { + godot_vector3 raw_dest; + Vector3 *dest = (Vector3 *)&raw_dest; + memnew_placement(dest, Vector3(p_self->operator Vector3())); + return raw_dest; +} + +godot_vector3i godotsharp_variant_as_vector3i(const Variant *p_self) { + godot_vector3i raw_dest; + Vector3i *dest = (Vector3i *)&raw_dest; + memnew_placement(dest, Vector3i(p_self->operator Vector3i())); + return raw_dest; +} + +godot_transform2d godotsharp_variant_as_transform2d(const Variant *p_self) { + godot_transform2d raw_dest; + Transform2D *dest = (Transform2D *)&raw_dest; + memnew_placement(dest, Transform2D(p_self->operator Transform2D())); + return raw_dest; +} + +godot_vector4 godotsharp_variant_as_vector4(const Variant *p_self) { + godot_vector4 raw_dest; + Vector4 *dest = (Vector4 *)&raw_dest; + memnew_placement(dest, Vector4(p_self->operator Vector4())); + return raw_dest; +} + +godot_vector4i godotsharp_variant_as_vector4i(const Variant *p_self) { + godot_vector4i raw_dest; + Vector4i *dest = (Vector4i *)&raw_dest; + memnew_placement(dest, Vector4i(p_self->operator Vector4i())); + return raw_dest; +} + +godot_plane godotsharp_variant_as_plane(const Variant *p_self) { + godot_plane raw_dest; + Plane *dest = (Plane *)&raw_dest; + memnew_placement(dest, Plane(p_self->operator Plane())); + return raw_dest; +} + +godot_quaternion godotsharp_variant_as_quaternion(const Variant *p_self) { + godot_quaternion raw_dest; + Quaternion *dest = (Quaternion *)&raw_dest; + memnew_placement(dest, Quaternion(p_self->operator Quaternion())); + return raw_dest; +} + +godot_aabb godotsharp_variant_as_aabb(const Variant *p_self) { + godot_aabb raw_dest; + AABB *dest = (AABB *)&raw_dest; + memnew_placement(dest, AABB(p_self->operator ::AABB())); + return raw_dest; +} + +godot_basis godotsharp_variant_as_basis(const Variant *p_self) { + godot_basis raw_dest; + Basis *dest = (Basis *)&raw_dest; + memnew_placement(dest, Basis(p_self->operator Basis())); + return raw_dest; +} + +godot_transform3d godotsharp_variant_as_transform3d(const Variant *p_self) { + godot_transform3d raw_dest; + Transform3D *dest = (Transform3D *)&raw_dest; + memnew_placement(dest, Transform3D(p_self->operator Transform3D())); + return raw_dest; +} + +godot_projection godotsharp_variant_as_projection(const Variant *p_self) { + godot_projection raw_dest; + Projection *dest = (Projection *)&raw_dest; + memnew_placement(dest, Projection(p_self->operator Projection())); + return raw_dest; +} + +godot_color godotsharp_variant_as_color(const Variant *p_self) { + godot_color raw_dest; + Color *dest = (Color *)&raw_dest; + memnew_placement(dest, Color(p_self->operator Color())); + return raw_dest; +} + +godot_string_name godotsharp_variant_as_string_name(const Variant *p_self) { + godot_string_name raw_dest; + StringName *dest = (StringName *)&raw_dest; + memnew_placement(dest, StringName(p_self->operator StringName())); + return raw_dest; +} + +godot_node_path godotsharp_variant_as_node_path(const Variant *p_self) { + godot_node_path raw_dest; + NodePath *dest = (NodePath *)&raw_dest; + memnew_placement(dest, NodePath(p_self->operator NodePath())); + return raw_dest; +} + +godot_rid godotsharp_variant_as_rid(const Variant *p_self) { + godot_rid raw_dest; + RID *dest = (RID *)&raw_dest; + memnew_placement(dest, RID(p_self->operator ::RID())); + return raw_dest; +} + +godot_callable godotsharp_variant_as_callable(const Variant *p_self) { + godot_callable raw_dest; + Callable *dest = (Callable *)&raw_dest; + memnew_placement(dest, Callable(p_self->operator Callable())); + return raw_dest; +} + +godot_signal godotsharp_variant_as_signal(const Variant *p_self) { + godot_signal raw_dest; + Signal *dest = (Signal *)&raw_dest; + memnew_placement(dest, Signal(p_self->operator Signal())); + return raw_dest; +} + +godot_dictionary godotsharp_variant_as_dictionary(const Variant *p_self) { + godot_dictionary raw_dest; + Dictionary *dest = (Dictionary *)&raw_dest; + memnew_placement(dest, Dictionary(p_self->operator Dictionary())); + return raw_dest; +} + +godot_array godotsharp_variant_as_array(const Variant *p_self) { + godot_array raw_dest; + Array *dest = (Array *)&raw_dest; + memnew_placement(dest, Array(p_self->operator Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_byte_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedByteArray *dest = (PackedByteArray *)&raw_dest; + memnew_placement(dest, PackedByteArray(p_self->operator PackedByteArray())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_int32_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedInt32Array *dest = (PackedInt32Array *)&raw_dest; + memnew_placement(dest, PackedInt32Array(p_self->operator PackedInt32Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_int64_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedInt64Array *dest = (PackedInt64Array *)&raw_dest; + memnew_placement(dest, PackedInt64Array(p_self->operator PackedInt64Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_float32_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedFloat32Array *dest = (PackedFloat32Array *)&raw_dest; + memnew_placement(dest, PackedFloat32Array(p_self->operator PackedFloat32Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_float64_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedFloat64Array *dest = (PackedFloat64Array *)&raw_dest; + memnew_placement(dest, PackedFloat64Array(p_self->operator PackedFloat64Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_string_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedStringArray *dest = (PackedStringArray *)&raw_dest; + memnew_placement(dest, PackedStringArray(p_self->operator PackedStringArray())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_vector2_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedVector2Array *dest = (PackedVector2Array *)&raw_dest; + memnew_placement(dest, PackedVector2Array(p_self->operator PackedVector2Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_vector3_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedVector3Array *dest = (PackedVector3Array *)&raw_dest; + memnew_placement(dest, PackedVector3Array(p_self->operator PackedVector3Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_color_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedColorArray *dest = (PackedColorArray *)&raw_dest; + memnew_placement(dest, PackedColorArray(p_self->operator PackedColorArray())); + return raw_dest; +} + +bool godotsharp_variant_equals(const godot_variant *p_a, const godot_variant *p_b) { + return *reinterpret_cast<const Variant *>(p_a) == *reinterpret_cast<const Variant *>(p_b); +} + +// string.h + +void godotsharp_string_new_with_utf16_chars(String *r_dest, const char16_t *p_contents) { + memnew_placement(r_dest, String()); + r_dest->parse_utf16(p_contents); +} + +// string_name.h + +void godotsharp_string_name_new_copy(StringName *r_dest, const StringName *p_src) { + memnew_placement(r_dest, StringName(*p_src)); +} + +// node_path.h + +void godotsharp_node_path_new_copy(NodePath *r_dest, const NodePath *p_src) { + memnew_placement(r_dest, NodePath(*p_src)); +} + +// array.h + +void godotsharp_array_new(Array *r_dest) { + memnew_placement(r_dest, Array); +} + +void godotsharp_array_new_copy(Array *r_dest, const Array *p_src) { + memnew_placement(r_dest, Array(*p_src)); +} + +godot_variant *godotsharp_array_ptrw(godot_array *p_self) { + return reinterpret_cast<godot_variant *>(&reinterpret_cast<Array *>(p_self)->operator[](0)); +} + +// dictionary.h + +void godotsharp_dictionary_new(Dictionary *r_dest) { + memnew_placement(r_dest, Dictionary); +} + +void godotsharp_dictionary_new_copy(Dictionary *r_dest, const Dictionary *p_src) { + memnew_placement(r_dest, Dictionary(*p_src)); +} + +// destroy functions + +void godotsharp_packed_byte_array_destroy(PackedByteArray *p_self) { + p_self->~PackedByteArray(); +} + +void godotsharp_packed_int32_array_destroy(PackedInt32Array *p_self) { + p_self->~PackedInt32Array(); +} + +void godotsharp_packed_int64_array_destroy(PackedInt64Array *p_self) { + p_self->~PackedInt64Array(); +} + +void godotsharp_packed_float32_array_destroy(PackedFloat32Array *p_self) { + p_self->~PackedFloat32Array(); +} + +void godotsharp_packed_float64_array_destroy(PackedFloat64Array *p_self) { + p_self->~PackedFloat64Array(); +} + +void godotsharp_packed_string_array_destroy(PackedStringArray *p_self) { + p_self->~PackedStringArray(); +} + +void godotsharp_packed_vector2_array_destroy(PackedVector2Array *p_self) { + p_self->~PackedVector2Array(); +} + +void godotsharp_packed_vector3_array_destroy(PackedVector3Array *p_self) { + p_self->~PackedVector3Array(); +} + +void godotsharp_packed_color_array_destroy(PackedColorArray *p_self) { + p_self->~PackedColorArray(); +} + +void godotsharp_variant_destroy(Variant *p_self) { + p_self->~Variant(); +} + +void godotsharp_string_destroy(String *p_self) { + p_self->~String(); +} + +void godotsharp_string_name_destroy(StringName *p_self) { + p_self->~StringName(); +} + +void godotsharp_node_path_destroy(NodePath *p_self) { + p_self->~NodePath(); +} + +void godotsharp_signal_destroy(Signal *p_self) { + p_self->~Signal(); +} + +void godotsharp_callable_destroy(Callable *p_self) { + p_self->~Callable(); +} + +void godotsharp_array_destroy(Array *p_self) { + p_self->~Array(); +} + +void godotsharp_dictionary_destroy(Dictionary *p_self) { + p_self->~Dictionary(); +} + +// Array + +int32_t godotsharp_array_add(Array *p_self, const Variant *p_item) { + p_self->append(*p_item); + return p_self->size(); +} + +void godotsharp_array_duplicate(const Array *p_self, bool p_deep, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->duplicate(p_deep))); +} + +int32_t godotsharp_array_index_of(const Array *p_self, const Variant *p_item) { + return p_self->find(*p_item); +} + +void godotsharp_array_insert(Array *p_self, int32_t p_index, const Variant *p_item) { + p_self->insert(p_index, *p_item); +} + +void godotsharp_array_remove_at(Array *p_self, int32_t p_index) { + p_self->remove_at(p_index); +} + +int32_t godotsharp_array_resize(Array *p_self, int32_t p_new_size) { + return (int32_t)p_self->resize(p_new_size); +} + +void godotsharp_array_shuffle(Array *p_self) { + p_self->shuffle(); +} + +void godotsharp_array_to_string(const Array *p_self, String *r_str) { + *r_str = Variant(*p_self).operator String(); +} + +// Dictionary + +bool godotsharp_dictionary_try_get_value(const Dictionary *p_self, const Variant *p_key, Variant *r_value) { + const Variant *ret = p_self->getptr(*p_key); + if (ret == nullptr) { + memnew_placement(r_value, Variant()); + return false; + } + memnew_placement(r_value, Variant(*ret)); + return true; +} + +void godotsharp_dictionary_set_value(Dictionary *p_self, const Variant *p_key, const Variant *p_value) { + p_self->operator[](*p_key) = *p_value; +} + +void godotsharp_dictionary_keys(const Dictionary *p_self, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->keys())); +} + +void godotsharp_dictionary_values(const Dictionary *p_self, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->values())); +} + +int32_t godotsharp_dictionary_count(const Dictionary *p_self) { + return p_self->size(); +} + +void godotsharp_dictionary_key_value_pair_at(const Dictionary *p_self, int32_t p_index, Variant *r_key, Variant *r_value) { + memnew_placement(r_key, Variant(p_self->get_key_at_index(p_index))); + memnew_placement(r_value, Variant(p_self->get_value_at_index(p_index))); +} + +void godotsharp_dictionary_add(Dictionary *p_self, const Variant *p_key, const Variant *p_value) { + p_self->operator[](*p_key) = *p_value; +} + +void godotsharp_dictionary_clear(Dictionary *p_self) { + p_self->clear(); +} + +bool godotsharp_dictionary_contains_key(const Dictionary *p_self, const Variant *p_key) { + return p_self->has(*p_key); +} + +void godotsharp_dictionary_duplicate(const Dictionary *p_self, bool p_deep, Dictionary *r_dest) { + memnew_placement(r_dest, Dictionary(p_self->duplicate(p_deep))); +} + +bool godotsharp_dictionary_remove_key(Dictionary *p_self, const Variant *p_key) { + return p_self->erase(*p_key); +} + +void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) { + *r_str = Variant(*p_self).operator String(); +} + +void godotsharp_string_md5_buffer(const String *p_self, PackedByteArray *r_md5_buffer) { + memnew_placement(r_md5_buffer, PackedByteArray(p_self->md5_buffer())); +} + +void godotsharp_string_md5_text(const String *p_self, String *r_md5_text) { + memnew_placement(r_md5_text, String(p_self->md5_text())); +} + +int32_t godotsharp_string_rfind(const String *p_self, const String *p_what, int32_t p_from) { + return p_self->rfind(*p_what, p_from); +} + +int32_t godotsharp_string_rfindn(const String *p_self, const String *p_what, int32_t p_from) { + return p_self->rfindn(*p_what, p_from); +} + +void godotsharp_string_sha256_buffer(const String *p_self, PackedByteArray *r_sha256_buffer) { + memnew_placement(r_sha256_buffer, PackedByteArray(p_self->sha256_buffer())); +} + +void godotsharp_string_sha256_text(const String *p_self, String *r_sha256_text) { + memnew_placement(r_sha256_text, String(p_self->sha256_text())); +} + +void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_path) { + memnew_placement(r_simplified_path, String(p_self->simplify_path())); +} + +void godotsharp_node_path_get_as_property_path(const NodePath *p_ptr, NodePath *r_dest) { + memnew_placement(r_dest, NodePath(p_ptr->get_as_property_path())); +} + +void godotsharp_node_path_get_concatenated_names(const NodePath *p_self, String *r_subnames) { + memnew_placement(r_subnames, String(p_self->get_concatenated_names())); +} + +void godotsharp_node_path_get_concatenated_subnames(const NodePath *p_self, String *r_subnames) { + memnew_placement(r_subnames, String(p_self->get_concatenated_subnames())); +} + +void godotsharp_node_path_get_name(const NodePath *p_self, uint32_t p_idx, String *r_name) { + memnew_placement(r_name, String(p_self->get_name(p_idx))); +} + +int32_t godotsharp_node_path_get_name_count(const NodePath *p_self) { + return p_self->get_name_count(); +} + +void godotsharp_node_path_get_subname(const NodePath *p_self, uint32_t p_idx, String *r_subname) { + memnew_placement(r_subname, String(p_self->get_subname(p_idx))); +} + +int32_t godotsharp_node_path_get_subname_count(const NodePath *p_self) { + return p_self->get_subname_count(); +} + +bool godotsharp_node_path_is_absolute(const NodePath *p_self) { + return p_self->is_absolute(); +} + +void godotsharp_randomize() { + Math::randomize(); +} + +uint32_t godotsharp_randi() { + return Math::rand(); +} + +float godotsharp_randf() { + return Math::randf(); +} + +int32_t godotsharp_randi_range(int32_t p_from, int32_t p_to) { + return Math::random(p_from, p_to); +} + +double godotsharp_randf_range(double p_from, double p_to) { + return Math::random(p_from, p_to); +} + +double godotsharp_randfn(double p_mean, double p_deviation) { + return Math::randfn(p_mean, p_deviation); +} + +void godotsharp_seed(uint64_t p_seed) { + Math::seed(p_seed); +} + +uint32_t godotsharp_rand_from_seed(uint64_t p_seed, uint64_t *r_new_seed) { + uint32_t ret = Math::rand_from_seed(&p_seed); + *r_new_seed = p_seed; + return ret; +} + +void godotsharp_weakref(Object *p_ptr, Ref<RefCounted> *r_weak_ref) { + if (!p_ptr) { + return; + } + + Ref<WeakRef> wref; + RefCounted *rc = Object::cast_to<RefCounted>(p_ptr); + + if (rc) { + Ref<RefCounted> r = rc; + if (!r.is_valid()) { + return; + } + + wref.instantiate(); + wref->set_ref(r); + } else { + wref.instantiate(); + wref->set_obj(p_ptr); + } + + memnew_placement(r_weak_ref, Ref<RefCounted>(wref)); +} + +void godotsharp_str(const godot_array *p_what, godot_string *r_ret) { + String &str = *memnew_placement(r_ret, String); + const Array &what = *reinterpret_cast<const Array *>(p_what); + + for (int i = 0; i < what.size(); i++) { + String os = what[i].operator String(); + + if (i == 0) { + str = os; + } else { + str += os; + } + } +} + +void godotsharp_print(const godot_string *p_what) { + print_line(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_print_rich(const godot_string *p_what) { + print_line_rich(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_printerr(const godot_string *p_what) { + print_error(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_printt(const godot_string *p_what) { + print_line(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_prints(const godot_string *p_what) { + print_line(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_printraw(const godot_string *p_what) { + OS::get_singleton()->print("%s", reinterpret_cast<const String *>(p_what)->utf8().get_data()); +} + +void godotsharp_pusherror(const godot_string *p_str) { + ERR_PRINT(*reinterpret_cast<const String *>(p_str)); +} + +void godotsharp_pushwarning(const godot_string *p_str) { + WARN_PRINT(*reinterpret_cast<const String *>(p_str)); +} + +void godotsharp_var2str(const godot_variant *p_var, godot_string *r_ret) { + const Variant &var = *reinterpret_cast<const Variant *>(p_var); + String &vars = *memnew_placement(r_ret, String); + VariantWriter::write_to_string(var, vars); +} + +void godotsharp_str2var(const godot_string *p_str, godot_variant *r_ret) { + Variant ret; + + VariantParser::StreamString ss; + ss.s = *reinterpret_cast<const String *>(p_str); + + String errs; + int line; + Error err = VariantParser::parse(&ss, ret, errs, line); + if (err != OK) { + String err_str = "Parse error at line " + itos(line) + ": " + errs + "."; + ERR_PRINT(err_str); + ret = err_str; + } + memnew_placement(r_ret, Variant(ret)); +} + +void godotsharp_var2bytes(const godot_variant *p_var, bool p_full_objects, godot_packed_array *r_bytes) { + const Variant &var = *reinterpret_cast<const Variant *>(p_var); + PackedByteArray &bytes = *memnew_placement(r_bytes, PackedByteArray); + + int len; + Error err = encode_variant(var, nullptr, len, p_full_objects); + ERR_FAIL_COND_MSG(err != OK, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); + + bytes.resize(len); + encode_variant(var, bytes.ptrw(), len, p_full_objects); +} + +void godotsharp_bytes2var(const godot_packed_array *p_bytes, bool p_allow_objects, godot_variant *r_ret) { + const PackedByteArray *bytes = reinterpret_cast<const PackedByteArray *>(p_bytes); + Variant ret; + Error err = decode_variant(ret, bytes->ptr(), bytes->size(), nullptr, p_allow_objects); + if (err != OK) { + ret = RTR("Not enough bytes for decoding bytes, or invalid format."); + } + memnew_placement(r_ret, Variant(ret)); +} + +int godotsharp_hash(const godot_variant *p_var) { + return reinterpret_cast<const Variant *>(p_var)->hash(); +} + +void godotsharp_convert(const godot_variant *p_what, int32_t p_type, godot_variant *r_ret) { + const Variant *args[1] = { reinterpret_cast<const Variant *>(p_what) }; + Callable::CallError ce; + Variant ret; + Variant::construct(Variant::Type(p_type), ret, args, 1, ce); + if (ce.error != Callable::CallError::CALL_OK) { + memnew_placement(r_ret, Variant); + ERR_FAIL_MSG("Unable to convert parameter from '" + + Variant::get_type_name(reinterpret_cast<const Variant *>(p_what)->get_type()) + + "' to '" + Variant::get_type_name(Variant::Type(p_type)) + "'."); + } + memnew_placement(r_ret, Variant(ret)); +} + +Object *godotsharp_instance_from_id(uint64_t p_instance_id) { + return ObjectDB::get_instance(ObjectID(p_instance_id)); +} + +void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) { +#ifdef DEBUG_ENABLED + // Cannot happen in C#; would get an ObjectDisposedException instead. + CRASH_COND(p_ptr == nullptr); +#endif + // Can't call 'Object::to_string()' here, as that can end up calling 'ToString' again resulting in an endless circular loop. + memnew_placement(r_str, + String("[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]")); +} + +#ifdef __cplusplus +} +#endif + +// The order in this array must match the declaration order of +// the methods in 'GodotSharp/Core/NativeInterop/NativeFuncs.cs'. +static const void *unmanaged_callbacks[]{ + (void *)godotsharp_method_bind_get_method, + (void *)godotsharp_get_class_constructor, + (void *)godotsharp_engine_get_singleton, + (void *)godotsharp_stack_info_vector_resize, + (void *)godotsharp_stack_info_vector_destroy, + (void *)godotsharp_internal_script_debugger_send_error, + (void *)godotsharp_internal_script_debugger_is_active, + (void *)godotsharp_internal_object_get_associated_gchandle, + (void *)godotsharp_internal_object_disposed, + (void *)godotsharp_internal_refcounted_disposed, + (void *)godotsharp_internal_signal_awaiter_connect, + (void *)godotsharp_internal_tie_native_managed_to_unmanaged, + (void *)godotsharp_internal_tie_user_managed_to_unmanaged, + (void *)godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup, + (void *)godotsharp_internal_unmanaged_get_script_instance_managed, + (void *)godotsharp_internal_unmanaged_get_instance_binding_managed, + (void *)godotsharp_internal_unmanaged_instance_binding_create_managed, + (void *)godotsharp_internal_new_csharp_script, + (void *)godotsharp_internal_script_load, + (void *)godotsharp_internal_reload_registered_script, + (void *)godotsharp_array_filter_godot_objects_by_native, + (void *)godotsharp_array_filter_godot_objects_by_non_native, + (void *)godotsharp_ref_new_from_ref_counted_ptr, + (void *)godotsharp_ref_destroy, + (void *)godotsharp_string_name_new_from_string, + (void *)godotsharp_node_path_new_from_string, + (void *)godotsharp_string_name_as_string, + (void *)godotsharp_node_path_as_string, + (void *)godotsharp_packed_byte_array_new_mem_copy, + (void *)godotsharp_packed_int32_array_new_mem_copy, + (void *)godotsharp_packed_int64_array_new_mem_copy, + (void *)godotsharp_packed_float32_array_new_mem_copy, + (void *)godotsharp_packed_float64_array_new_mem_copy, + (void *)godotsharp_packed_vector2_array_new_mem_copy, + (void *)godotsharp_packed_vector3_array_new_mem_copy, + (void *)godotsharp_packed_color_array_new_mem_copy, + (void *)godotsharp_packed_string_array_add, + (void *)godotsharp_callable_new_with_delegate, + (void *)godotsharp_callable_get_data_for_marshalling, + (void *)godotsharp_callable_call, + (void *)godotsharp_callable_call_deferred, + (void *)godotsharp_method_bind_ptrcall, + (void *)godotsharp_method_bind_call, + (void *)godotsharp_variant_new_string_name, + (void *)godotsharp_variant_new_copy, + (void *)godotsharp_variant_new_node_path, + (void *)godotsharp_variant_new_object, + (void *)godotsharp_variant_new_transform2d, + (void *)godotsharp_variant_new_vector4, + (void *)godotsharp_variant_new_vector4i, + (void *)godotsharp_variant_new_basis, + (void *)godotsharp_variant_new_transform3d, + (void *)godotsharp_variant_new_projection, + (void *)godotsharp_variant_new_aabb, + (void *)godotsharp_variant_new_dictionary, + (void *)godotsharp_variant_new_array, + (void *)godotsharp_variant_new_packed_byte_array, + (void *)godotsharp_variant_new_packed_int32_array, + (void *)godotsharp_variant_new_packed_int64_array, + (void *)godotsharp_variant_new_packed_float32_array, + (void *)godotsharp_variant_new_packed_float64_array, + (void *)godotsharp_variant_new_packed_string_array, + (void *)godotsharp_variant_new_packed_vector2_array, + (void *)godotsharp_variant_new_packed_vector3_array, + (void *)godotsharp_variant_new_packed_color_array, + (void *)godotsharp_variant_as_bool, + (void *)godotsharp_variant_as_int, + (void *)godotsharp_variant_as_float, + (void *)godotsharp_variant_as_string, + (void *)godotsharp_variant_as_vector2, + (void *)godotsharp_variant_as_vector2i, + (void *)godotsharp_variant_as_rect2, + (void *)godotsharp_variant_as_rect2i, + (void *)godotsharp_variant_as_vector3, + (void *)godotsharp_variant_as_vector3i, + (void *)godotsharp_variant_as_transform2d, + (void *)godotsharp_variant_as_vector4, + (void *)godotsharp_variant_as_vector4i, + (void *)godotsharp_variant_as_plane, + (void *)godotsharp_variant_as_quaternion, + (void *)godotsharp_variant_as_aabb, + (void *)godotsharp_variant_as_basis, + (void *)godotsharp_variant_as_transform3d, + (void *)godotsharp_variant_as_projection, + (void *)godotsharp_variant_as_color, + (void *)godotsharp_variant_as_string_name, + (void *)godotsharp_variant_as_node_path, + (void *)godotsharp_variant_as_rid, + (void *)godotsharp_variant_as_callable, + (void *)godotsharp_variant_as_signal, + (void *)godotsharp_variant_as_dictionary, + (void *)godotsharp_variant_as_array, + (void *)godotsharp_variant_as_packed_byte_array, + (void *)godotsharp_variant_as_packed_int32_array, + (void *)godotsharp_variant_as_packed_int64_array, + (void *)godotsharp_variant_as_packed_float32_array, + (void *)godotsharp_variant_as_packed_float64_array, + (void *)godotsharp_variant_as_packed_string_array, + (void *)godotsharp_variant_as_packed_vector2_array, + (void *)godotsharp_variant_as_packed_vector3_array, + (void *)godotsharp_variant_as_packed_color_array, + (void *)godotsharp_variant_equals, + (void *)godotsharp_string_new_with_utf16_chars, + (void *)godotsharp_string_name_new_copy, + (void *)godotsharp_node_path_new_copy, + (void *)godotsharp_array_new, + (void *)godotsharp_array_new_copy, + (void *)godotsharp_array_ptrw, + (void *)godotsharp_dictionary_new, + (void *)godotsharp_dictionary_new_copy, + (void *)godotsharp_packed_byte_array_destroy, + (void *)godotsharp_packed_int32_array_destroy, + (void *)godotsharp_packed_int64_array_destroy, + (void *)godotsharp_packed_float32_array_destroy, + (void *)godotsharp_packed_float64_array_destroy, + (void *)godotsharp_packed_string_array_destroy, + (void *)godotsharp_packed_vector2_array_destroy, + (void *)godotsharp_packed_vector3_array_destroy, + (void *)godotsharp_packed_color_array_destroy, + (void *)godotsharp_variant_destroy, + (void *)godotsharp_string_destroy, + (void *)godotsharp_string_name_destroy, + (void *)godotsharp_node_path_destroy, + (void *)godotsharp_signal_destroy, + (void *)godotsharp_callable_destroy, + (void *)godotsharp_array_destroy, + (void *)godotsharp_dictionary_destroy, + (void *)godotsharp_array_add, + (void *)godotsharp_array_duplicate, + (void *)godotsharp_array_index_of, + (void *)godotsharp_array_insert, + (void *)godotsharp_array_remove_at, + (void *)godotsharp_array_resize, + (void *)godotsharp_array_shuffle, + (void *)godotsharp_array_to_string, + (void *)godotsharp_dictionary_try_get_value, + (void *)godotsharp_dictionary_set_value, + (void *)godotsharp_dictionary_keys, + (void *)godotsharp_dictionary_values, + (void *)godotsharp_dictionary_count, + (void *)godotsharp_dictionary_key_value_pair_at, + (void *)godotsharp_dictionary_add, + (void *)godotsharp_dictionary_clear, + (void *)godotsharp_dictionary_contains_key, + (void *)godotsharp_dictionary_duplicate, + (void *)godotsharp_dictionary_remove_key, + (void *)godotsharp_dictionary_to_string, + (void *)godotsharp_string_md5_buffer, + (void *)godotsharp_string_md5_text, + (void *)godotsharp_string_rfind, + (void *)godotsharp_string_rfindn, + (void *)godotsharp_string_sha256_buffer, + (void *)godotsharp_string_sha256_text, + (void *)godotsharp_string_simplify_path, + (void *)godotsharp_node_path_get_as_property_path, + (void *)godotsharp_node_path_get_concatenated_names, + (void *)godotsharp_node_path_get_concatenated_subnames, + (void *)godotsharp_node_path_get_name, + (void *)godotsharp_node_path_get_name_count, + (void *)godotsharp_node_path_get_subname, + (void *)godotsharp_node_path_get_subname_count, + (void *)godotsharp_node_path_is_absolute, + (void *)godotsharp_bytes2var, + (void *)godotsharp_convert, + (void *)godotsharp_hash, + (void *)godotsharp_instance_from_id, + (void *)godotsharp_print, + (void *)godotsharp_print_rich, + (void *)godotsharp_printerr, + (void *)godotsharp_printraw, + (void *)godotsharp_prints, + (void *)godotsharp_printt, + (void *)godotsharp_randf, + (void *)godotsharp_randi, + (void *)godotsharp_randomize, + (void *)godotsharp_randf_range, + (void *)godotsharp_randfn, + (void *)godotsharp_randi_range, + (void *)godotsharp_rand_from_seed, + (void *)godotsharp_seed, + (void *)godotsharp_weakref, + (void *)godotsharp_str, + (void *)godotsharp_str2var, + (void *)godotsharp_var2bytes, + (void *)godotsharp_var2str, + (void *)godotsharp_pusherror, + (void *)godotsharp_pushwarning, + (void *)godotsharp_object_to_string, +}; + +const void **godotsharp::get_runtime_interop_funcs(int32_t &r_size) { + r_size = sizeof(unmanaged_callbacks); + return unmanaged_callbacks; +} diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/glue/runtime_interop.h index bf21283080..9725ced593 100644 --- a/modules/mono/mono_gd/gd_mono_header.h +++ b/modules/mono/glue/runtime_interop.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gd_mono_header.h */ +/* runtime_interop.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GD_MONO_HEADER_H -#define GD_MONO_HEADER_H +#ifndef RUNTIME_INTEROP_H +#define RUNTIME_INTEROP_H -#include <cstdint> +#include "core/typedefs.h" -#ifdef WIN32 -#define GD_MONO_STDCALL __stdcall -#else -#define GD_MONO_STDCALL -#endif +namespace godotsharp { +const void **get_runtime_interop_funcs(int32_t &r_size); +} -class GDMonoAssembly; -class GDMonoClass; -class GDMonoField; -class GDMonoMethod; -class GDMonoProperty; - -class IMonoClassMember; - -#include "managed_type.h" - -#endif // GD_MONO_HEADER_H +#endif // RUNTIME_INTEROP_H diff --git a/modules/mono/glue/scene_tree_glue.cpp b/modules/mono/glue/scene_tree_glue.cpp deleted file mode 100644 index c60e7c4869..0000000000 --- a/modules/mono/glue/scene_tree_glue.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/*************************************************************************/ -/* scene_tree_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/object/class_db.h" -#include "core/string/string_name.h" -#include "core/variant/array.h" -#include "scene/main/node.h" -#include "scene/main/scene_tree.h" - -#include "../csharp_script.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" - -Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringName *group, MonoReflectionType *refltype) { - List<Node *> nodes; - Array ret; - - // Retrieve all the nodes in the group - ptr->get_nodes_in_group(*group, &nodes); - - // No need to bother if the group is empty - if (!nodes.is_empty()) { - MonoType *elem_type = mono_reflection_type_get_type(refltype); - MonoClass *mono_class = mono_class_from_mono_type(elem_type); - GDMonoClass *klass = GDMono::get_singleton()->get_class(mono_class); - - if (klass == GDMonoUtils::get_class_native_base(klass)) { - // If we're trying to get native objects, just check the inheritance list - StringName native_class_name = GDMonoUtils::get_native_godot_class_name(klass); - for (int i = 0; i < nodes.size(); ++i) { - if (ClassDB::is_parent_class(nodes[i]->get_class(), native_class_name)) { - ret.push_back(nodes[i]); - } - } - } else { - // If we're trying to get csharpscript instances, get the mono object and compare the classes - for (int i = 0; i < nodes.size(); ++i) { - CSharpInstance *si = CAST_CSHARP_INSTANCE(nodes[i]->get_script_instance()); - - if (si != nullptr) { - MonoObject *obj = si->get_mono_object(); - if (obj != nullptr && mono_object_get_class(obj) == mono_class) { - ret.push_back(nodes[i]); - } - } - } - } - } - - return memnew(Array(ret)); -} - -void godot_register_scene_tree_icalls() { - GDMonoUtils::add_internal_call("Godot.SceneTree::godot_icall_SceneTree_get_nodes_in_group_Generic", godot_icall_SceneTree_get_nodes_in_group_Generic); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp deleted file mode 100644 index fc6b13ceb3..0000000000 --- a/modules/mono/glue/string_glue.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************/ -/* string_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/string/ustring.h" -#include "core/templates/vector.h" -#include "core/variant/variant.h" - -#include "../mono_gd/gd_mono_marshal.h" - -MonoArray *godot_icall_String_md5_buffer(MonoString *p_str) { - Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_buffer(); - // TODO Check possible Array/Vector<uint8_t> problem? - return GDMonoMarshal::Array_to_mono_array(Variant(ret)); -} - -MonoString *godot_icall_String_md5_text(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_text(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from) { - String what = GDMonoMarshal::mono_string_to_godot(p_what); - return GDMonoMarshal::mono_string_to_godot(p_str).rfind(what, p_from); -} - -int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from) { - String what = GDMonoMarshal::mono_string_to_godot(p_what); - return GDMonoMarshal::mono_string_to_godot(p_str).rfindn(what, p_from); -} - -MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str) { - Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_buffer(); - return GDMonoMarshal::Array_to_mono_array(Variant(ret)); -} - -MonoString *godot_icall_String_sha256_text(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_text(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -MonoString *godot_icall_String_simplify_path(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).simplify_path(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -void godot_register_string_icalls() { - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", godot_icall_String_md5_buffer); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", godot_icall_String_md5_text); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", godot_icall_String_rfind); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", godot_icall_String_rfindn); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", godot_icall_String_sha256_buffer); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", godot_icall_String_sha256_text); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_simplify_path", godot_icall_String_simplify_path); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_name_glue.cpp b/modules/mono/glue/string_name_glue.cpp deleted file mode 100644 index 46d15316ba..0000000000 --- a/modules/mono/glue/string_name_glue.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/*************************************************************************/ -/* string_name_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/string/string_name.h" -#include "core/string/ustring.h" - -#include "../mono_gd/gd_mono_marshal.h" - -StringName *godot_icall_StringName_Ctor(MonoString *p_path) { - return memnew(StringName(GDMonoMarshal::mono_string_to_godot(p_path))); -} - -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() { - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Ctor", godot_icall_StringName_Ctor); - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Dtor", godot_icall_StringName_Dtor); - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_operator_String", godot_icall_StringName_operator_String); - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_is_empty", godot_icall_StringName_is_empty); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index e5f1abe8d7..a81a52e4b8 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -33,14 +33,10 @@ #define BINDINGS_NAMESPACE "Godot" #define BINDINGS_NAMESPACE_COLLECTIONS BINDINGS_NAMESPACE ".Collections" -#define BINDINGS_GLOBAL_SCOPE_CLASS "GD" -#define BINDINGS_PTR_FIELD "ptr" -#define BINDINGS_NATIVE_NAME_FIELD "nativeName" #define API_SOLUTION_NAME "GodotSharp" #define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" #define TOOLS_ASM_NAME "GodotTools" -#define TOOLS_PROJECT_EDITOR_ASM_NAME "GodotTools.ProjectEditor" #define BINDINGS_CLASS_NATIVECALLS "NativeCalls" #define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index f17b24e399..71576c2f80 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -96,8 +96,6 @@ class _GodotSharpDirs { public: String res_data_dir; String res_metadata_dir; - String res_assemblies_base_dir; - String res_assemblies_dir; String res_config_dir; String res_temp_dir; String res_temp_assemblies_base_dir; @@ -105,15 +103,14 @@ public: String mono_user_dir; String mono_logs_dir; + String api_assemblies_base_dir; + String api_assemblies_dir; + #ifdef TOOLS_ENABLED String mono_solutions_dir; String build_logs_dir; - String sln_filepath; - String csproj_filepath; - String data_editor_tools_dir; - String data_editor_prebuilt_api_dir; #else // Equivalent of res_assemblies_dir, but in the data directory rather than in 'res://'. // Only defined on export templates. Used when exporting assemblies outside of PCKs. @@ -131,8 +128,6 @@ private: _GodotSharpDirs() { res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().plus_file("mono"); res_metadata_dir = res_data_dir.plus_file("metadata"); - res_assemblies_base_dir = res_data_dir.plus_file("assemblies"); - res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config()); res_config_dir = res_data_dir.plus_file("etc").plus_file("mono"); // TODO use paths from csproj @@ -140,6 +135,8 @@ private: res_temp_assemblies_base_dir = res_temp_dir.plus_file("bin"); res_temp_assemblies_dir = res_temp_assemblies_base_dir.plus_file(_get_expected_build_config()); + api_assemblies_base_dir = res_data_dir.plus_file("assemblies"); + #ifdef JAVASCRIPT_ENABLED mono_user_dir = "user://"; #else @@ -151,16 +148,7 @@ private: mono_solutions_dir = mono_user_dir.plus_file("solutions"); build_logs_dir = mono_user_dir.plus_file("build_logs"); - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.is_empty()) { - appname_safe = "UnnamedProject"; - } - String base_path = ProjectSettings::get_singleton()->globalize_path("res://"); - - sln_filepath = base_path.plus_file(appname_safe + ".sln"); - csproj_filepath = base_path.plus_file(appname_safe + ".csproj"); #endif String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); @@ -169,7 +157,7 @@ private: String data_dir_root = exe_dir.plus_file("GodotSharp"); data_editor_tools_dir = data_dir_root.plus_file("Tools"); - data_editor_prebuilt_api_dir = data_dir_root.plus_file("Api"); + api_assemblies_base_dir = data_dir_root.plus_file("Api"); String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); @@ -189,8 +177,8 @@ private: data_editor_tools_dir = exe_dir.plus_file("../Resources/GodotSharp/Tools"); } - if (!DirAccess::exists(data_editor_prebuilt_api_dir)) { - data_editor_prebuilt_api_dir = exe_dir.plus_file("../Resources/GodotSharp/Api"); + if (!DirAccess::exists(api_assemblies_base_dir)) { + api_assemblies_base_dir = exe_dir.plus_file("../Resources/GodotSharp/Api"); } if (!DirAccess::exists(data_mono_root_dir)) { @@ -234,6 +222,12 @@ private: #endif #endif + +#ifdef TOOLS_ENABLED + api_assemblies_dir = api_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config()); +#else + api_assemblies_dir = data_dir_root; +#endif } public: @@ -251,14 +245,6 @@ String get_res_metadata_dir() { return _GodotSharpDirs::get_singleton().res_metadata_dir; } -String get_res_assemblies_base_dir() { - return _GodotSharpDirs::get_singleton().res_assemblies_base_dir; -} - -String get_res_assemblies_dir() { - return _GodotSharpDirs::get_singleton().res_assemblies_dir; -} - String get_res_config_dir() { return _GodotSharpDirs::get_singleton().res_config_dir; } @@ -275,6 +261,14 @@ String get_res_temp_assemblies_dir() { return _GodotSharpDirs::get_singleton().res_temp_assemblies_dir; } +String get_api_assemblies_dir() { + return _GodotSharpDirs::get_singleton().api_assemblies_dir; +} + +String get_api_assemblies_base_dir() { + return _GodotSharpDirs::get_singleton().api_assemblies_base_dir; +} + String get_mono_user_dir() { return _GodotSharpDirs::get_singleton().mono_user_dir; } @@ -292,21 +286,9 @@ String get_build_logs_dir() { return _GodotSharpDirs::get_singleton().build_logs_dir; } -String get_project_sln_path() { - return _GodotSharpDirs::get_singleton().sln_filepath; -} - -String get_project_csproj_path() { - return _GodotSharpDirs::get_singleton().csproj_filepath; -} - String get_data_editor_tools_dir() { return _GodotSharpDirs::get_singleton().data_editor_tools_dir; } - -String get_data_editor_prebuilt_api_dir() { - return _GodotSharpDirs::get_singleton().data_editor_prebuilt_api_dir; -} #else String get_data_game_assemblies_dir() { return _GodotSharpDirs::get_singleton().data_game_assemblies_dir; diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h index da25e0778f..03e62ffd82 100644 --- a/modules/mono/godotsharp_dirs.h +++ b/modules/mono/godotsharp_dirs.h @@ -37,13 +37,14 @@ namespace GodotSharpDirs { String get_res_data_dir(); String get_res_metadata_dir(); -String get_res_assemblies_base_dir(); -String get_res_assemblies_dir(); String get_res_config_dir(); String get_res_temp_dir(); String get_res_temp_assemblies_base_dir(); String get_res_temp_assemblies_dir(); +String get_api_assemblies_dir(); +String get_api_assemblies_base_dir(); + String get_mono_user_dir(); String get_mono_logs_dir(); @@ -51,11 +52,7 @@ String get_mono_logs_dir(); String get_mono_solutions_dir(); String get_build_logs_dir(); -String get_project_sln_path(); -String get_project_csproj_path(); - String get_data_editor_tools_dir(); -String get_data_editor_prebuilt_api_dir(); #else String get_data_game_assemblies_dir(); #endif diff --git a/modules/mono/interop_types.h b/modules/mono/interop_types.h new file mode 100644 index 0000000000..6942a91559 --- /dev/null +++ b/modules/mono/interop_types.h @@ -0,0 +1,208 @@ +/*************************************************************************/ +/* interop_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 INTEROP_TYPES_H +#define INTEROP_TYPES_H + +#include "core/math/math_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stdint.h> + +// This is taken from the old GDNative, which was removed. + +#define GODOT_VARIANT_SIZE (sizeof(real_t) * 4 + sizeof(int64_t)) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VARIANT_SIZE]; +} godot_variant; + +#define GODOT_ARRAY_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_ARRAY_SIZE]; +} godot_array; + +#define GODOT_DICTIONARY_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_DICTIONARY_SIZE]; +} godot_dictionary; + +#define GODOT_STRING_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_STRING_SIZE]; +} godot_string; + +#define GODOT_STRING_NAME_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_STRING_NAME_SIZE]; +} godot_string_name; + +#define GODOT_PACKED_ARRAY_SIZE (2 * sizeof(void *)) + +typedef struct { + uint8_t _dont_touch_that[GODOT_PACKED_ARRAY_SIZE]; +} godot_packed_array; + +#define GODOT_VECTOR2_SIZE (sizeof(real_t) * 2) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR2_SIZE]; +} godot_vector2; + +#define GODOT_VECTOR2I_SIZE (sizeof(int32_t) * 2) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR2I_SIZE]; +} godot_vector2i; + +#define GODOT_RECT2_SIZE (sizeof(real_t) * 4) + +typedef struct godot_rect2 { + uint8_t _dont_touch_that[GODOT_RECT2_SIZE]; +} godot_rect2; + +#define GODOT_RECT2I_SIZE (sizeof(int32_t) * 4) + +typedef struct godot_rect2i { + uint8_t _dont_touch_that[GODOT_RECT2I_SIZE]; +} godot_rect2i; + +#define GODOT_VECTOR3_SIZE (sizeof(real_t) * 3) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR3_SIZE]; +} godot_vector3; + +#define GODOT_VECTOR3I_SIZE (sizeof(int32_t) * 3) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR3I_SIZE]; +} godot_vector3i; + +#define GODOT_TRANSFORM2D_SIZE (sizeof(real_t) * 6) + +typedef struct { + uint8_t _dont_touch_that[GODOT_TRANSFORM2D_SIZE]; +} godot_transform2d; + +#define GODOT_VECTOR4_SIZE (sizeof(real_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR4_SIZE]; +} godot_vector4; + +#define GODOT_VECTOR4I_SIZE (sizeof(int32_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR4I_SIZE]; +} godot_vector4i; + +#define GODOT_PLANE_SIZE (sizeof(real_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_PLANE_SIZE]; +} godot_plane; + +#define GODOT_QUATERNION_SIZE (sizeof(real_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_QUATERNION_SIZE]; +} godot_quaternion; + +#define GODOT_AABB_SIZE (sizeof(real_t) * 6) + +typedef struct { + uint8_t _dont_touch_that[GODOT_AABB_SIZE]; +} godot_aabb; + +#define GODOT_BASIS_SIZE (sizeof(real_t) * 9) + +typedef struct { + uint8_t _dont_touch_that[GODOT_BASIS_SIZE]; +} godot_basis; + +#define GODOT_TRANSFORM3D_SIZE (sizeof(real_t) * 12) + +typedef struct { + uint8_t _dont_touch_that[GODOT_TRANSFORM3D_SIZE]; +} godot_transform3d; + +#define GODOT_PROJECTION_SIZE (sizeof(real_t) * 4 * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_PROJECTION_SIZE]; +} godot_projection; + +// Colors should always use 32-bit floats, so don't use real_t here. +#define GODOT_COLOR_SIZE (sizeof(float) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_COLOR_SIZE]; +} godot_color; + +#define GODOT_NODE_PATH_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_NODE_PATH_SIZE]; +} godot_node_path; + +#define GODOT_RID_SIZE sizeof(uint64_t) + +typedef struct { + uint8_t _dont_touch_that[GODOT_RID_SIZE]; +} godot_rid; + +// Alignment hardcoded in `core/variant/callable.h`. +#define GODOT_CALLABLE_SIZE (16) + +typedef struct { + uint8_t _dont_touch_that[GODOT_CALLABLE_SIZE]; +} godot_callable; + +// Alignment hardcoded in `core/variant/callable.h`. +#define GODOT_SIGNAL_SIZE (16) + +typedef struct { + uint8_t _dont_touch_that[GODOT_SIGNAL_SIZE]; +} godot_signal; + +#ifdef __cplusplus +} +#endif + +#endif // INTEROP_TYPES_H diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index c159bb9eea..9305dc645a 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -31,8 +31,7 @@ #include "managed_callable.h" #include "csharp_script.h" -#include "mono_gd/gd_mono_marshal.h" -#include "mono_gd/gd_mono_utils.h" +#include "mono_gd/gd_mono_cache.h" #ifdef GD_MONO_HOT_RELOAD SelfList<ManagedCallable>::List ManagedCallable::instances; @@ -44,18 +43,16 @@ bool ManagedCallable::compare_equal(const CallableCustom *p_a, const CallableCus 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) { + if (!a->delegate_handle.value || !b->delegate_handle.value) { + if (!a->delegate_handle.value && !b->delegate_handle.value) { return true; } return false; } // Call Delegate's 'Equals' - return GDMonoUtils::mono_delegate_equal(delegate_a, delegate_b); + return GDMonoCache::managed_callbacks.DelegateUtils_DelegateEquals( + a->delegate_handle, b->delegate_handle); } bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -66,8 +63,7 @@ bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCust } uint32_t ManagedCallable::hash() const { - uint32_t hash = delegate_invoke->get_name().hash(); - return hash_murmur3_one_64(delegate_handle.handle, hash); + return hash_murmur3_one_64((uint64_t)delegate_handle.value); } String ManagedCallable::get_as_text() const { @@ -91,41 +87,24 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant 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(); + ERR_FAIL_COND(delegate_handle.value == nullptr); - MonoException *exc = nullptr; - MonoObject *ret = delegate_invoke->invoke(delegate, p_arguments, &exc); + GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs( + delegate_handle, p_arguments, p_argcount, &r_return_value); - 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; - } + 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 +void ManagedCallable::release_delegate_handle() { + if (delegate_handle.value) { + GDMonoCache::managed_callbacks.GCHandleBridge_FreeGCHandle(delegate_handle); + delegate_handle = { nullptr }; + } } -ManagedCallable::ManagedCallable(MonoDelegate *p_delegate) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_delegate == nullptr); -#endif - - set_delegate(p_delegate); - +// Why you do this clang-format... +/* clang-format off */ +ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle) : delegate_handle(p_delegate_handle) { #ifdef GD_MONO_HOT_RELOAD { MutexLock lock(instances_mutex); @@ -133,6 +112,7 @@ ManagedCallable::ManagedCallable(MonoDelegate *p_delegate) { } #endif } +/* clang-format on */ ManagedCallable::~ManagedCallable() { #ifdef GD_MONO_HOT_RELOAD @@ -143,5 +123,5 @@ ManagedCallable::~ManagedCallable() { } #endif - delegate_handle.release(); + release_delegate_handle(); } diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h index 11bee6cf60..aa3344f4d5 100644 --- a/modules/mono/managed_callable.h +++ b/modules/mono/managed_callable.h @@ -31,19 +31,15 @@ #ifndef MANAGED_CALLABLE_H #define MANAGED_CALLABLE_H -#include <mono/metadata/object.h> - #include "core/os/mutex.h" #include "core/templates/self_list.h" #include "core/variant/callable.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 = nullptr; + GCHandleIntPtr delegate_handle; #ifdef GD_MONO_HOT_RELOAD SelfList<ManagedCallable> self_instance = this; @@ -60,9 +56,7 @@ public: 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); + _FORCE_INLINE_ GCHandleIntPtr get_delegate() const { return delegate_handle; } static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); @@ -70,7 +64,9 @@ public: static constexpr CompareEqualFunc compare_equal_func_ptr = &ManagedCallable::compare_equal; static constexpr CompareEqualFunc compare_less_func_ptr = &ManagedCallable::compare_less; - ManagedCallable(MonoDelegate *p_delegate); + void release_delegate_handle(); + + ManagedCallable(GCHandleIntPtr p_delegate_handle); ~ManagedCallable(); }; diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp index f3dafa6ecf..9cf0a641b9 100644 --- a/modules/mono/mono_gc_handle.cpp +++ b/modules/mono/mono_gc_handle.cpp @@ -31,34 +31,20 @@ #include "mono_gc_handle.h" #include "mono_gd/gd_mono.h" +#include "mono_gd/gd_mono_cache.h" void MonoGCHandleData::release() { #ifdef DEBUG_ENABLED - CRASH_COND(handle && GDMono::get_singleton() == nullptr); + CRASH_COND(handle.value && GDMono::get_singleton() == nullptr); #endif - if (handle && GDMono::get_singleton()->is_runtime_initialized()) { - GDMonoUtils::free_gchandle(handle); - handle = 0; + if (handle.value && GDMonoCache::godot_api_cache_updated && + GDMono::get_singleton()->is_runtime_initialized()) { + free_gchandle(handle); + handle.value = nullptr; } } - -MonoGCHandleData MonoGCHandleData::new_strong_handle(MonoObject *p_object) { - return MonoGCHandleData(GDMonoUtils::new_strong_gchandle(p_object), gdmono::GCHandleType::STRONG_HANDLE); -} - -MonoGCHandleData MonoGCHandleData::new_strong_handle_pinned(MonoObject *p_object) { - return MonoGCHandleData(GDMonoUtils::new_strong_gchandle_pinned(p_object), gdmono::GCHandleType::STRONG_HANDLE); -} - -MonoGCHandleData MonoGCHandleData::new_weak_handle(MonoObject *p_object) { - return MonoGCHandleData(GDMonoUtils::new_weak_gchandle(p_object), gdmono::GCHandleType::WEAK_HANDLE); -} - -Ref<MonoGCHandleRef> MonoGCHandleRef::create_strong(MonoObject *p_object) { - return memnew(MonoGCHandleRef(MonoGCHandleData::new_strong_handle(p_object))); -} - -Ref<MonoGCHandleRef> MonoGCHandleRef::create_weak(MonoObject *p_object) { - return memnew(MonoGCHandleRef(MonoGCHandleData::new_weak_handle(p_object))); +void MonoGCHandleData::free_gchandle(GCHandleIntPtr p_gchandle) { + CRASH_COND(!GDMonoCache::godot_api_cache_updated); + GDMonoCache::managed_callbacks.GCHandleBridge_FreeGCHandle(p_gchandle); } diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h index e2aff1d19d..4e4c13fee6 100644 --- a/modules/mono/mono_gc_handle.h +++ b/modules/mono/mono_gc_handle.h @@ -31,8 +31,6 @@ #ifndef MONO_GC_HANDLE_H #define MONO_GC_HANDLE_H -#include <mono/jit/jit.h> - #include "core/object/ref_counted.h" namespace gdmono { @@ -44,18 +42,32 @@ enum class GCHandleType : char { }; } +extern "C" { +struct GCHandleIntPtr { + void *value; + + _FORCE_INLINE_ bool operator==(const GCHandleIntPtr &p_other) { return value == p_other.value; } + _FORCE_INLINE_ bool operator!=(const GCHandleIntPtr &p_other) { return value != p_other.value; } + + GCHandleIntPtr() = delete; +}; +} + +static_assert(sizeof(GCHandleIntPtr) == sizeof(void *)); + // Manual release of the GC handle must be done when using this struct struct MonoGCHandleData { - uint32_t handle = 0; + GCHandleIntPtr handle = { nullptr }; gdmono::GCHandleType type = gdmono::GCHandleType::NIL; - _FORCE_INLINE_ bool is_released() const { return !handle; } + _FORCE_INLINE_ bool is_released() const { return !handle.value; } _FORCE_INLINE_ bool is_weak() const { return type == gdmono::GCHandleType::WEAK_HANDLE; } - - _FORCE_INLINE_ MonoObject *get_target() const { return handle ? mono_gchandle_get_target(handle) : nullptr; } + _FORCE_INLINE_ GCHandleIntPtr get_intptr() const { return handle; } void release(); + static void free_gchandle(GCHandleIntPtr p_gchandle); + void operator=(const MonoGCHandleData &p_other) { #ifdef DEBUG_ENABLED CRASH_COND(!is_released()); @@ -68,40 +80,10 @@ struct MonoGCHandleData { MonoGCHandleData() {} - MonoGCHandleData(uint32_t p_handle, gdmono::GCHandleType p_type) : + MonoGCHandleData(GCHandleIntPtr 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 RefCounted { - GDCLASS(MonoGCHandleRef, RefCounted); - - 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 // MONO_GC_HANDLE_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index d3d3bb2bef..0532cc915b 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -30,13 +30,6 @@ #include "gd_mono.h" -#include <mono/metadata/environment.h> -#include <mono/metadata/exception.h> -#include <mono/metadata/mono-config.h> -#include <mono/metadata/mono-debug.h> -#include <mono/metadata/mono-gc.h> -#include <mono/metadata/profiler.h> - #include "core/config/project_settings.h" #include "core/debugger/engine_debugger.h" #include "core/io/dir_access.h" @@ -45,1077 +38,519 @@ #include "core/os/thread.h" #include "../csharp_script.h" +#include "../glue/runtime_interop.h" #include "../godotsharp_dirs.h" #include "../utils/path_utils.h" #include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" +#ifdef TOOLS_ENABLED +#include <nethost.h> +#endif + +#include <coreclr_delegates.h> +#include <hostfxr.h> +#ifdef UNIX_ENABLED +#include <dlfcn.h> +#endif + +// TODO mobile +#if 0 #ifdef ANDROID_ENABLED -#include "android_mono_config.h" #include "support/android_support.h" #elif defined(IOS_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 turned 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 = nullptr; namespace { - -#if defined(JAVASCRIPT_ENABLED) -extern "C" { -void mono_wasm_load_runtime(const char *managed_path, int enable_debugging); -} -#endif - -#if !defined(JAVASCRIPT_ENABLED) - -void gd_mono_setup_runtime_main_args() { - CharString execpath = OS::get_singleton()->get_executable_path().utf8(); - - List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); - - List<CharString> cmdline_args_utf8; - Vector<char *> main_args; - main_args.resize(cmdline_args.size() + 1); - - main_args.write[0] = execpath.ptrw(); - - int i = 1; - for (const String &E : cmdline_args) { - CharString &stored = cmdline_args_utf8.push_back(E.utf8())->get(); - main_args.write[i] = stored.ptrw(); - i++; - } - - mono_runtime_set_main_args(main_args.size(), main_args.ptrw()); -} - -void gd_mono_profiler_init() { - String profiler_args = GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd"); - bool profiler_enabled = GLOBAL_DEF("mono/profiler/enabled", false); - if (profiler_enabled) { - mono_profiler_load(profiler_args.utf8()); - return; - } - - const String env_var_name = "MONO_ENV_OPTIONS"; - if (OS::get_singleton()->has_environment(env_var_name)) { - const String mono_env_ops = OS::get_singleton()->get_environment(env_var_name); - // Usually MONO_ENV_OPTIONS looks like: --profile=jb:prof=timeline,ctl=remote,host=127.0.0.1:55467 - const String prefix = "--profile="; - if (mono_env_ops.begins_with(prefix)) { - const String ops = mono_env_ops.substr(prefix.length(), mono_env_ops.length()); - mono_profiler_load(ops.utf8()); - } - } -} - -void gd_mono_debug_init() { - CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); - - if (da_args.length()) { - OS::get_singleton()->set_environment("GODOT_MONO_DEBUGGER_AGENT", String()); - } - -#ifdef TOOLS_ENABLED - int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685); - bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); - int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); - - if (Engine::get_singleton()->is_editor_hint() || - ProjectSettings::get_singleton()->get_resource_path().is_empty() || - Engine::get_singleton()->is_project_manager_hint()) { - if (da_args.size() == 0) { - return; - } - } - - if (da_args.length() == 0) { - da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) + - ",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n")) - .utf8(); - } +hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = nullptr; +hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = nullptr; +hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr; +hostfxr_close_fn hostfxr_close = nullptr; + +#ifdef _WIN32 +static_assert(sizeof(char_t) == sizeof(char16_t)); +using HostFxrCharString = Char16String; +#define HOSTFXR_STR(m_str) L##m_str #else - if (da_args.length() == 0) { - return; // Exported games don't use the project settings to setup the debugger agent - } +static_assert(sizeof(char_t) == sizeof(char)); +using HostFxrCharString = CharString; +#define HOSTFXR_STR(m_str) m_str #endif - // Debugging enabled - - mono_debug_init(MONO_DEBUG_FORMAT_MONO); - - // --debugger-agent=help - const char *options[] = { - "--soft-breakpoints", - da_args.get_data() - }; - mono_jit_parse_options(2, (char **)options); -} - -#endif // !defined(JAVASCRIPT_ENABLED) - -#if defined(JAVASCRIPT_ENABLED) -MonoDomain *gd_initialize_mono_runtime() { - const char *vfs_prefix = "managed"; - int enable_debugging = 0; - - // TODO: Provide a way to enable debugging on WASM release builds. -#ifdef DEBUG_ENABLED - enable_debugging = 1; +HostFxrCharString str_to_hostfxr(const String &p_str) { +#ifdef _WIN32 + return p_str.utf16(); +#else + return p_str.utf8(); #endif - - mono_wasm_load_runtime(vfs_prefix, enable_debugging); - - return mono_get_root_domain(); } -#else -MonoDomain *gd_initialize_mono_runtime() { - gd_mono_debug_init(); -#if defined(IOS_ENABLED) || defined(ANDROID_ENABLED) - // I don't know whether this actually matters or not - const char *runtime_version = "mobile"; +#ifdef TOOLS_ENABLED +String str_from_hostfxr(const char_t *p_buffer) { +#ifdef _WIN32 + return String::utf16((const char16_t *)p_buffer); #else - const char *runtime_version = "v4.0.30319"; + return String::utf8((const char *)p_buffer); #endif - - return mono_jit_init_version("GodotEngine.RootDomain", runtime_version); } #endif -} // namespace - -void GDMono::add_mono_shared_libs_dir_to_path() { - // TODO: Replace this with a mono_dl_fallback - - // By default Mono seems to search shared libraries in the following directories: - // Current working directory, @executable_path@ and PATH - // The parent directory of the image file (assembly where the dllimport method is declared) - // @executable_path@/../lib - // @executable_path@/../Libraries (__MACH__ only) - // This does not work when embedding Mono unless we use the same directory structure. - // To fix this we append the directory containing our shared libraries to PATH. - -#if defined(WINDOWS_ENABLED) || defined(UNIX_ENABLED) - String path_var("PATH"); - String path_value = OS::get_singleton()->get_environment(path_var); - -#ifdef WINDOWS_ENABLED - path_value += ';'; +const char_t *get_data(const HostFxrCharString &p_char_str) { + return (const char_t *)p_char_str.get_data(); +} - String bundled_bin_dir = GodotSharpDirs::get_data_mono_bin_dir(); #ifdef TOOLS_ENABLED - if (DirAccess::exists(bundled_bin_dir)) { - path_value += bundled_bin_dir; - } else { - path_value += mono_reg_info.bin_dir; - } -#else - if (DirAccess::exists(bundled_bin_dir)) { - path_value += bundled_bin_dir; - } -#endif // TOOLS_ENABLED +String find_hostfxr(size_t p_known_buffet_size, get_hostfxr_parameters *p_get_hostfxr_params) { + // Pre-allocate a large buffer for the path to hostfxr + Vector<char_t> buffer; + buffer.resize(p_known_buffet_size); -#else - path_value += ':'; + int rc = get_hostfxr_path(buffer.ptrw(), &p_known_buffet_size, p_get_hostfxr_params); - String bundled_lib_dir = GodotSharpDirs::get_data_mono_lib_dir(); - if (DirAccess::exists(bundled_lib_dir)) { - path_value += bundled_lib_dir; - } else { - // TODO: Do we need to add the lib dir when using the system installed Mono on Unix platforms? - } -#endif // WINDOWS_ENABLED + ERR_FAIL_COND_V_MSG(rc != 0, String(), "get_hostfxr_path failed with code: " + itos(rc)); - OS::get_singleton()->set_environment(path_var, path_value); -#endif // WINDOWS_ENABLED || UNIX_ENABLED + return str_from_hostfxr(buffer.ptr()); } +#endif -void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_dir) { - String bundled_assembly_rootdir = GodotSharpDirs::get_data_mono_lib_dir(); - String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir(); - +String find_hostfxr() { #ifdef TOOLS_ENABLED - -#if defined(WINDOWS_ENABLED) - mono_reg_info = MonoRegUtils::find_mono(); - - if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) { - r_assembly_rootdir = mono_reg_info.assembly_dir; - } - - if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) { - r_config_dir = mono_reg_info.config_dir; - } -#elif defined(MACOS_ENABLED) - const char *c_assembly_rootdir = mono_assembly_getrootdir(); - const char *c_config_dir = mono_get_config_dir(); - - if (!c_assembly_rootdir || !c_config_dir || !DirAccess::exists(c_assembly_rootdir) || !DirAccess::exists(c_config_dir)) { - Vector<const char *> locations; - locations.push_back("/Library/Frameworks/Mono.framework/Versions/Current/"); - locations.push_back("/usr/local/var/homebrew/linked/mono/"); - - for (int i = 0; i < locations.size(); i++) { - String hint_assembly_rootdir = path::join(locations[i], "lib"); - String hint_mscorlib_path = path::join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll"); - String hint_config_dir = path::join(locations[i], "etc"); - - if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) { - r_assembly_rootdir = hint_assembly_rootdir; - r_config_dir = hint_config_dir; - break; + const int CoreHostLibMissingFailure = 0x80008083; + const int HostApiBufferTooSmall = 0x80008098; + + size_t buffer_size = 0; + int rc = get_hostfxr_path(nullptr, &buffer_size, nullptr); + + if (rc == HostApiBufferTooSmall) { + return find_hostfxr(buffer_size, nullptr); + } + + if (rc == CoreHostLibMissingFailure) { + // Apparently `get_hostfxr_path` doesn't look for dotnet in `PATH`? (I suppose it needs the + // `DOTNET_ROOT` environment variable). If it fails, we try to find the dotnet executable + // in `PATH` ourselves and pass its location as `dotnet_root` to `get_hostfxr_path`. + String dotnet_exe = path::find_executable("dotnet"); + + if (!dotnet_exe.is_empty()) { + // The file found in PATH may be a symlink + dotnet_exe = path::abspath(path::realpath(dotnet_exe)); + + // TODO: + // Sometimes, the symlink may not point to the dotnet executable in the dotnet root. + // That's the case with snaps. The snap install should have been found with the + // previous `get_hostfxr_path`, but it would still be better to do this properly + // and use something like `dotnet --list-sdks/runtimes` to find the actual location. + // This way we could also check if the proper sdk or runtime is installed. This would + // allow us to fail gracefully and show some helpful information in the editor. + + HostFxrCharString dotnet_root = str_to_hostfxr(dotnet_exe.get_base_dir()); + + get_hostfxr_parameters get_hostfxr_parameters = { + sizeof(get_hostfxr_parameters), + nullptr, + get_data(dotnet_root) + }; + + buffer_size = 0; + rc = get_hostfxr_path(nullptr, &buffer_size, &get_hostfxr_parameters); + if (rc == HostApiBufferTooSmall) { + return find_hostfxr(buffer_size, &get_hostfxr_parameters); } } } -#endif - if (DirAccess::exists(bundled_assembly_rootdir)) { - r_assembly_rootdir = bundled_assembly_rootdir; + if (rc == CoreHostLibMissingFailure) { + ERR_PRINT(String() + ".NET: One of the dependent libraries is missing. " + + "Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic " + + "libraries are not present in the expected locations."); } - if (DirAccess::exists(bundled_config_dir)) { - r_config_dir = bundled_config_dir; - } - -#ifdef WINDOWS_ENABLED - if (r_assembly_rootdir.is_empty() || r_config_dir.is_empty()) { - ERR_PRINT("Cannot find Mono in the registry."); - // Assertion: if they are not set, then they weren't found in the registry - CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0); - } -#endif // WINDOWS_ENABLED - + return String(); #else - // Export templates always use the bundled directories - r_assembly_rootdir = bundled_assembly_rootdir; - r_config_dir = bundled_config_dir; -#endif -} - -void GDMono::initialize() { - ERR_FAIL_NULL(Engine::get_singleton()); - - print_verbose("Mono: Initializing module..."); - - char *runtime_build_info = mono_get_runtime_build_info(); - print_verbose("Mono JIT compiler version " + String(runtime_build_info)); - mono_free(runtime_build_info); - - _init_godot_api_hashes(); - _init_exception_policy(); - - GDMonoLog::get_singleton()->initialize(); - -#if !defined(JAVASCRIPT_ENABLED) - String assembly_rootdir; - String config_dir; - 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() : 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()); +#if defined(WINDOWS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .plus_file("hostfxr.dll"); +#elif defined(MACOS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .plus_file("libhostfxr.dylib"); +#elif defined(UNIX_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .plus_file("libhostfxr.so"); #else - mono_config_parse(nullptr); +#error "Platform not supported (yet?)" #endif -#if defined(ANDROID_ENABLED) - gdmono::android::support::initialize(); -#elif defined(IOS_ENABLED) - gdmono::ios::support::initialize(); -#endif + if (FileAccess::exists(probe_path)) { + return probe_path; + } - GDMonoAssembly::initialize(); + return String(); -#if !defined(JAVASCRIPT_ENABLED) - gd_mono_profiler_init(); #endif +} - 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. - // We only initialize the Mono runtime if we can find mscorlib. Otherwise it would crash. - if (GDMonoAssembly::find_assembly("mscorlib.dll").is_empty()) { - print_verbose("Mono: Skipping runtime initialization because 'mscorlib.dll' could not be found"); - return; - } -#endif +bool load_hostfxr(void *&r_hostfxr_dll_handle) { + String hostfxr_path = find_hostfxr(); -#if !defined(NO_MONO_THREADS_SUSPEND_WORKAROUND) - // FIXME: Temporary workaround. See: https://github.com/godotengine/godot/issues/29812 - if (!OS::get_singleton()->has_environment("MONO_THREADS_SUSPEND")) { - OS::get_singleton()->set_environment("MONO_THREADS_SUSPEND", "preemptive"); + if (hostfxr_path.is_empty()) { + return false; } -#endif - // NOTE: Internal calls must be registered after the Mono runtime initialization. - // Otherwise registration fails with the error: 'assertion 'hash != nullptr' failed'. + print_verbose("Found hostfxr: " + hostfxr_path); - root_domain = gd_initialize_mono_runtime(); - ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime."); + Error err = OS::get_singleton()->open_dynamic_library(hostfxr_path, r_hostfxr_dll_handle); - GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread()); - -#if !defined(JAVASCRIPT_ENABLED) - gd_mono_setup_runtime_main_args(); // Required for System.Environment.GetCommandLineArgs -#endif + if (err != OK) { + return false; + } - runtime_initialized = true; + void *lib = r_hostfxr_dll_handle; - print_verbose("Mono: Runtime initialized"); + void *symbol = nullptr; -#if defined(ANDROID_ENABLED) - gdmono::android::support::register_internal_calls(); -#endif + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_dotnet_command_line", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)symbol; - // 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."); + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_runtime_config", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)symbol; -#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 + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_get_runtime_delegate", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)symbol; - _register_internal_calls(); + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_close", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_close = (hostfxr_close_fn)symbol; - print_verbose("Mono: INITIALIZED"); + return (hostfxr_initialize_for_runtime_config && + hostfxr_get_runtime_delegate && + hostfxr_close); } -void GDMono::initialize_load_assemblies() { -#ifndef MONO_GLUE_ENABLED - CRASH_NOW_MSG("Mono: This binary was built with 'mono_glue=no'; cannot load assemblies."); -#endif - - // Load assemblies. The API and tools assemblies are required, - // the application is aborted if these assemblies cannot be loaded. - - _load_api_assemblies(); - -#if defined(TOOLS_ENABLED) - bool tool_assemblies_loaded = _load_tools_assemblies(); - CRASH_COND_MSG(!tool_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); - - if (Engine::get_singleton()->is_project_manager_hint()) { - return; - } -#endif - - // Load the project's main assembly. This doesn't necessarily need to succeed. - // The game may not be using .NET at all, or if the project does use .NET and - // we're running in the editor, it may just happen to be it wasn't built yet. - if (!_load_project_assembly()) { - if (OS::get_singleton()->is_stdout_verbose()) { - print_error("Mono: Failed to load project assembly"); - } - } -} - -bool GDMono::_are_api_assemblies_out_of_sync() { - bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoCache::cached_data.godot_api_cache_updated); #ifdef TOOLS_ENABLED - if (!out_of_sync) { - out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync; +load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) { + hostfxr_handle cxt = nullptr; + int rc = hostfxr_initialize_for_runtime_config(p_config_path, nullptr, &cxt); + if (rc != 0 || cxt == nullptr) { + hostfxr_close(cxt); + ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_runtime_config failed with code: " + itos(rc)); } -#endif - return out_of_sync; -} -namespace GodotSharpBindings { -#ifdef MONO_GLUE_ENABLED + void *load_assembly_and_get_function_pointer = nullptr; -uint64_t get_core_api_hash(); -#ifdef TOOLS_ENABLED -uint64_t get_editor_api_hash(); -#endif -uint32_t get_bindings_version(); -uint32_t get_cs_glue_version(); - -void register_generated_icalls(); + rc = hostfxr_get_runtime_delegate(cxt, + hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer); + if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) { + ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc)); + } -#else + hostfxr_close(cxt); -uint64_t get_core_api_hash() { - GD_UNREACHABLE(); -} -#ifdef TOOLS_ENABLED -uint64_t get_editor_api_hash() { - GD_UNREACHABLE(); -} -#endif -uint32_t get_bindings_version() { - GD_UNREACHABLE(); -} - -uint32_t get_cs_glue_version() { - GD_UNREACHABLE(); + return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; } +#else +load_assembly_and_get_function_pointer_fn initialize_hostfxr_self_contained( + const char_t *p_main_assembly_path) { + hostfxr_handle cxt = nullptr; -void register_generated_icalls() { - /* Fine, just do nothing */ -} + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); -#endif // MONO_GLUE_ENABLED -} // namespace GodotSharpBindings + List<HostFxrCharString> argv_store; + Vector<const char_t *> argv; + argv.resize(cmdline_args.size() + 1); -void GDMono::_register_internal_calls() { - GodotSharpBindings::register_generated_icalls(); -} + argv.write[0] = p_main_assembly_path; -void GDMono::_init_godot_api_hashes() { -#if defined(MONO_GLUE_ENABLED) && defined(DEBUG_METHODS_ENABLED) - if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) { - ERR_PRINT("Mono: Core API hash mismatch."); + int i = 1; + for (const String &E : cmdline_args) { + HostFxrCharString &stored = argv_store.push_back(str_to_hostfxr(E))->get(); + argv.write[i] = stored.ptr(); + i++; } -#ifdef TOOLS_ENABLED - if (get_api_editor_hash() != GodotSharpBindings::get_editor_api_hash()) { - ERR_PRINT("Mono: Editor API hash mismatch."); + int rc = hostfxr_initialize_for_dotnet_command_line(argv.size(), argv.ptrw(), nullptr, &cxt); + if (rc != 0 || cxt == nullptr) { + hostfxr_close(cxt); + ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_dotnet_command_line failed with code: " + itos(rc)); } -#endif // TOOLS_ENABLED -#endif // MONO_GLUE_ENABLED && DEBUG_METHODS_ENABLED -} -void GDMono::_init_exception_policy() { - PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/runtime/unhandled_exception_policy", PROPERTY_HINT_ENUM, - vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR)); - unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP); - ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop); + void *load_assembly_and_get_function_pointer = nullptr; - if (Engine::get_singleton()->is_editor_hint()) { - // Unhandled exceptions should not terminate the editor - unhandled_exception_policy = POLICY_LOG_ERROR; + rc = hostfxr_get_runtime_delegate(cxt, + hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer); + if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) { + ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc)); } -} - -void GDMono::add_assembly(int32_t p_domain_id, GDMonoAssembly *p_assembly) { - assemblies[p_domain_id][p_assembly->get_name()] = p_assembly; -} -GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) { - if (p_name == "mscorlib" && corlib_assembly) { - return corlib_assembly; - } + hostfxr_close(cxt); - MonoDomain *domain = mono_domain_get(); - int32_t domain_id = domain ? mono_domain_get_id(domain) : 0; - GDMonoAssembly **result = assemblies[domain_id].getptr(p_name); - return result ? *result : nullptr; + return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; } - -bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) { -#ifdef DEBUG_ENABLED - CRASH_COND(!r_assembly); #endif - MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); - bool result = load_assembly(p_name, aname, r_assembly, p_refonly); - mono_assembly_name_free(aname); - mono_free(aname); - - return result; -} - -bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) { -#ifdef DEBUG_ENABLED - CRASH_COND(!r_assembly); +#ifdef TOOLS_ENABLED +using godot_plugins_initialize_fn = bool (*)(void *, bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *, const void **, int32_t); +#else +using godot_plugins_initialize_fn = bool (*)(void *, GDMonoCache::ManagedCallbacks *, const void **, int32_t); #endif - return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs()); -} +#ifdef TOOLS_ENABLED +godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) { + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; -bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) { -#ifdef DEBUG_ENABLED - CRASH_COND(!r_assembly); -#endif + HostFxrCharString godot_plugins_path = str_to_hostfxr( + GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.dll")); - print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); + HostFxrCharString config_path = str_to_hostfxr( + GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.runtimeconfig.json")); - GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs); + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = + initialize_hostfxr_for_config(get_data(config_path)); + ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, nullptr); - if (!assembly) { - return false; - } + r_runtime_initialized = true; - *r_assembly = assembly; + print_verbose(".NET: hostfxr initialized"); - print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); + int rc = load_assembly_and_get_function_pointer(get_data(godot_plugins_path), + HOSTFXR_STR("GodotPlugins.Main, GodotPlugins"), + HOSTFXR_STR("InitializeFromEngine"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + (void **)&godot_plugins_initialize); + ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer"); - return true; + return godot_plugins_initialize; } +#else +static String get_assembly_name() { + String assembly_name = ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); -bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly) { - CRASH_COND(!r_assembly); - - print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); - - GDMonoAssembly *assembly = GDMonoAssembly::load_from(p_name, p_path, p_refonly); - - if (!assembly) { - return false; + if (assembly_name.is_empty()) { + assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); } - *r_assembly = assembly; - - print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); - - return true; + return assembly_name; } -ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, ApiAssemblyInfo::Type p_api_type) { - ApiAssemblyInfo::Version api_assembly_version; +godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) { + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; - const char *nativecalls_name = p_api_type == ApiAssemblyInfo::API_CORE - ? BINDINGS_CLASS_NATIVECALLS - : BINDINGS_CLASS_NATIVECALLS_EDITOR; + String assembly_name = get_assembly_name(); - GDMonoClass *nativecalls_klass = p_api_assembly->get_class(BINDINGS_NAMESPACE, nativecalls_name); + HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir() + .plus_file(assembly_name + ".dll")); - 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(nullptr)); - } + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = + initialize_hostfxr_self_contained(get_data(assembly_path)); + ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, 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(nullptr)); - } + r_runtime_initialized = true; - 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(nullptr)); - } - } + print_verbose(".NET: hostfxr initialized"); - return api_assembly_version; -} + int rc = load_assembly_and_get_function_pointer(get_data(assembly_path), + str_to_hostfxr("GodotPlugins.Game.Main, " + assembly_name), + HOSTFXR_STR("InitializeFromGameProject"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + (void **)&godot_plugins_initialize); + ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer"); -String ApiAssemblyInfo::to_string(ApiAssemblyInfo::Type p_type) { - return p_type == ApiAssemblyInfo::API_CORE ? "API_CORE" : "API_EDITOR"; + return godot_plugins_initialize; } -bool GDMono::_load_corlib_assembly() { - if (corlib_assembly) { - return true; - } - - bool success = load_assembly("mscorlib", &corlib_assembly); - - if (success) { - GDMonoCache::update_corlib_cache(); - } - - return success; -} +godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) { + String assembly_name = get_assembly_name(); -#ifdef TOOLS_ENABLED -bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config) { - String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); - - String assembly_name = p_api_type == ApiAssemblyInfo::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; +#if defined(WINDOWS_ENABLED) + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dll"); +#elif defined(MACOS_ENABLED) + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dylib"); +#elif defined(UNIX_ENABLED) + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".so"); +#else +#error "Platform not supported (yet?)" +#endif - // Create destination directory if needed - if (!DirAccess::exists(dst_dir)) { - Ref<DirAccess> da = DirAccess::create_for_path(dst_dir); - Error err = da->make_dir_recursive(dst_dir); + if (FileAccess::exists(native_aot_so_path)) { + Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle); if (err != OK) { - ERR_PRINT("Failed to create destination directory for the API assemblies. Error: " + itos(err) + "."); - return false; + return nullptr; } - } - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + void *lib = r_aot_dll_handle; - String xml_file = assembly_name + ".xml"; - if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) { - WARN_PRINT("Failed to copy '" + xml_file + "'."); - } + void *symbol = nullptr; - String pdb_file = assembly_name + ".pdb"; - if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) { - WARN_PRINT("Failed to copy '" + pdb_file + "'."); + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol); + ERR_FAIL_COND_V(err != OK, nullptr); + return (godot_plugins_initialize_fn)symbol; } - String assembly_file = assembly_name + ".dll"; - if (da->copy(src_dir.plus_file(assembly_file), dst_dir.plus_file(assembly_file)) != OK) { - ERR_PRINT("Failed to copy '" + assembly_file + "'."); - return false; - } - - return true; + return nullptr; } +#endif -static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool &r_out_of_sync) { - String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path)) { - return false; - } - - String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg"); +} // namespace - if (!FileAccess::exists(cached_api_hash_path)) { +static bool _on_core_api_assembly_loaded() { + if (!GDMonoCache::godot_api_cache_updated) { return false; } - Ref<ConfigFile> cfg; - cfg.instantiate(); - Error cfg_err = cfg->load(cached_api_hash_path); - ERR_FAIL_COND_V(cfg_err != OK, false); - - // Checking the modified time is good enough - if (FileAccess::get_modified_time(core_api_assembly_path) != (uint64_t)cfg->get_value("core", "modified_time") || - FileAccess::get_modified_time(editor_api_assembly_path) != (uint64_t)cfg->get_value("editor", "modified_time")) { - return false; - } + bool debug; +#ifdef DEBUG_ENABLED + debug = true; +#else + debug = false; +#endif - r_out_of_sync = GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("core", "bindings_version") || - GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("core", "cs_glue_version") || - GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("editor", "bindings_version") || - GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("editor", "cs_glue_version") || - GodotSharpBindings::get_core_api_hash() != (uint64_t)cfg->get_value("core", "api_hash") || - GodotSharpBindings::get_editor_api_hash() != (uint64_t)cfg->get_value("editor", "api_hash"); + GDMonoCache::managed_callbacks.GD_OnCoreApiAssemblyLoaded(debug); return true; } -static void create_cached_api_hash_for(const String &p_api_assemblies_dir) { - String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg"); - - Ref<ConfigFile> cfg; - cfg.instantiate(); - - cfg->set_value("core", "modified_time", FileAccess::get_modified_time(core_api_assembly_path)); - cfg->set_value("editor", "modified_time", FileAccess::get_modified_time(editor_api_assembly_path)); - - cfg->set_value("core", "bindings_version", GodotSharpBindings::get_bindings_version()); - cfg->set_value("core", "cs_glue_version", GodotSharpBindings::get_cs_glue_version()); - cfg->set_value("editor", "bindings_version", GodotSharpBindings::get_bindings_version()); - cfg->set_value("editor", "cs_glue_version", GodotSharpBindings::get_cs_glue_version()); - - // This assumes the prebuilt api assemblies we copied to the project are not out of sync - cfg->set_value("core", "api_hash", GodotSharpBindings::get_core_api_hash()); - cfg->set_value("editor", "api_hash", GodotSharpBindings::get_editor_api_hash()); - - Error err = cfg->save(cached_api_hash_path); - ERR_FAIL_COND(err != OK); -} - -bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config) { - MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.CheckApiAssemblies"); - ERR_FAIL_NULL_V(temp_domain, "Failed to create temporary domain to check API assemblies"); - _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain); - - _GDMONO_SCOPE_DOMAIN_(temp_domain); - - GDMono::LoadedApiAssembly temp_core_api_assembly; - 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: */ nullptr)) { - return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync; - } - - return true; // Failed to load, assume they're outdated assemblies -} - -String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync, const bool *p_editor_api_out_of_sync) { -#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \ - ( \ - (m_out_of_sync ? String("The assembly is invalidated ") : String("The assembly was not found ")) + \ - (m_prebuilt_exists ? String("and the prebuilt assemblies are missing.") : String("and we failed to copy the prebuilt assemblies."))) +void GDMono::initialize() { + print_verbose(".NET: Initializing module..."); - String dst_assemblies_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); + _init_godot_api_hashes(); - String core_assembly_path = dst_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String editor_assembly_path = dst_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; - bool api_assemblies_out_of_sync = false; + if (!load_hostfxr(hostfxr_dll_handle)) { +#if !defined(TOOLS_ENABLED) + godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle); - if (p_core_api_out_of_sync && p_editor_api_out_of_sync) { - api_assemblies_out_of_sync = p_core_api_out_of_sync || p_editor_api_out_of_sync; - } else if (FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) { - // Determine if they're out of sync - if (!try_get_cached_api_hash_for(dst_assemblies_dir, api_assemblies_out_of_sync)) { - api_assemblies_out_of_sync = _temp_domain_load_are_assemblies_out_of_sync(p_config); + if (godot_plugins_initialize != nullptr) { + is_native_aot = true; + } else { + ERR_FAIL_MSG(".NET: Failed to load hostfxr"); } +#else + ERR_FAIL_MSG(".NET: Failed to load hostfxr"); +#endif } - // Note: Even if only one of the assemblies if missing or out of sync, we update both - - if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) { - return String(); // No update needed - } - - print_verbose("Updating '" + p_config + "' API assemblies"); - - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { - return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ false); + if (!is_native_aot) { + godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized); + ERR_FAIL_NULL(godot_plugins_initialize); } - // Copy the prebuilt Api - if (!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_CORE, p_config) || - !copy_prebuilt_api_assembly(ApiAssemblyInfo::API_EDITOR, p_config)) { - return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ true); - } + int32_t interop_funcs_size = 0; + const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size); - // Cache the api hash of the assemblies we just copied - create_cached_api_hash_for(dst_assemblies_dir); + GDMonoCache::ManagedCallbacks managed_callbacks{}; - return String(); // Updated successfully + void *godot_dll_handle = nullptr; -#undef FAIL_REASON -} +#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(IOS_ENABLED) + // Managed code can access it on its own on other platforms + godot_dll_handle = dlopen(nullptr, RTLD_NOW); #endif -bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) { - if (r_loaded_api_assembly.assembly) { - return true; - } - #ifdef TOOLS_ENABLED - // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date - - // If running the project manager, load it from the prebuilt API directory - String assembly_dir = !Engine::get_singleton()->is_project_manager_hint() - ? GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) - : GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - - String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - - bool success = FileAccess::exists(assembly_path) && - load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly); + gdmono::PluginCallbacks plugin_callbacks_res; + bool init_ok = godot_plugins_initialize(godot_dll_handle, + Engine::get_singleton()->is_editor_hint(), + &plugin_callbacks_res, &managed_callbacks, + interop_funcs, interop_funcs_size); + ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed"); + + plugin_callbacks = plugin_callbacks_res; #else - bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &r_loaded_api_assembly.assembly, p_refonly); + bool init_ok = godot_plugins_initialize(godot_dll_handle, &managed_callbacks, + interop_funcs, interop_funcs_size); + ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed"); #endif - if (success) { - ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_CORE); - r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || - GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; - } else { - r_loaded_api_assembly.out_of_sync = false; - } + GDMonoCache::update_godot_api_cache(managed_callbacks); - return success; -} + print_verbose(".NET: GodotPlugins initialized"); -#ifdef TOOLS_ENABLED -bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) { - if (r_loaded_api_assembly.assembly) { - return true; - } - - // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date - - // If running the project manager, load it from the prebuilt API directory - String assembly_dir = !Engine::get_singleton()->is_project_manager_hint() - ? GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) - : GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - - String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - bool success = FileAccess::exists(assembly_path) && - load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly); - - if (success) { - ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_EDITOR); - r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || - GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; - } else { - r_loaded_api_assembly.out_of_sync = false; - } - - return success; + _on_core_api_assembly_loaded(); } -#endif -bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly, - const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback) { - if (!_load_core_api_assembly(r_core_api_assembly, p_config, p_refonly)) { - if (OS::get_singleton()->is_stdout_verbose()) { - print_error("Mono: Failed to load Core API assembly"); - } - return false; +#ifdef TOOLS_ENABLED +void GDMono::initialize_load_assemblies() { + if (Engine::get_singleton()->is_project_manager_hint()) { + return; } -#ifdef TOOLS_ENABLED - if (!_load_editor_api_assembly(r_editor_api_assembly, p_config, p_refonly)) { + // Load the project's main assembly. This doesn't necessarily need to succeed. + // The game may not be using .NET at all, or if the project does use .NET and + // we're running in the editor, it may just happen to be it wasn't built yet. + if (!_load_project_assembly()) { if (OS::get_singleton()->is_stdout_verbose()) { - print_error("Mono: Failed to load Editor API assembly"); + print_error(".NET: Failed to load project assembly"); } - return false; } - - if (r_editor_api_assembly.out_of_sync) { - return false; - } -#endif - - // Check if the core API assembly is out of sync only after trying to load the - // editor API assembly. Otherwise, if both assemblies are out of sync, we would - // only update the former as we won't know the latter also needs to be updated. - if (r_core_api_assembly.out_of_sync) { - return false; - } - - if (p_callback) { - return p_callback(); - } - - return true; } - -bool GDMono::_on_core_api_assembly_loaded() { - GDMonoCache::update_godot_api_cache(); - - if (!GDMonoCache::cached_data.godot_api_cache_updated) { - return false; - } - - get_singleton()->_install_trace_listener(); - - return true; -} - -bool GDMono::_try_load_api_assemblies_preset() { - return _try_load_api_assemblies(core_api_assembly, editor_api_assembly, - get_expected_api_build_config(), /* refonly: */ false, _on_core_api_assembly_loaded); -} - -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) { - // 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. - - // Shouldn't happen. The project manager loads the prebuilt API assemblies - CRASH_COND_MSG(Engine::get_singleton()->is_project_manager_hint(), "Failed to load one of the prebuilt API assemblies."); - - // 1. Unload the scripts domain - Error domain_unload_err = _unload_scripts_domain(); - CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain."); - - // 2. Update the API assemblies - String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync); - CRASH_COND_MSG(!update_error.is_empty(), update_error); - - // 3. Load the scripts domain again - Error domain_load_err = _load_scripts_domain(); - CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); - - // 4. Try loading the updated assemblies - api_assemblies_loaded = _try_load_api_assemblies_preset(); - } #endif - if (!api_assemblies_loaded) { - // welp... too bad - - if (_are_api_assemblies_out_of_sync()) { - if (core_api_assembly.out_of_sync) { - ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync."); - } else if (!GDMonoCache::cached_data.godot_api_cache_updated) { - ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed."); - } +void GDMono::_init_godot_api_hashes() { +#ifdef DEBUG_METHODS_ENABLED + get_api_core_hash(); #ifdef TOOLS_ENABLED - if (editor_api_assembly.out_of_sync) { - ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync."); - } -#endif - - CRASH_NOW(); - } else { - CRASH_NOW_MSG("Failed to load one of the API assemblies."); - } - } + get_api_editor_hash(); +#endif // TOOLS_ENABLED +#endif // DEBUG_METHODS_ENABLED } #ifdef TOOLS_ENABLED -bool GDMono::_load_tools_assemblies() { - if (tools_assembly && tools_project_editor_assembly) { - return true; - } - - bool success = load_assembly(TOOLS_ASM_NAME, &tools_assembly) && - load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_assembly); - - return success; -} -#endif - bool GDMono::_load_project_assembly() { - if (project_assembly) { - return true; - } - - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.is_empty()) { - appname_safe = "UnnamedProject"; - } - - bool success = load_assembly(appname_safe, &project_assembly); - - if (success) { - mono_assembly_set_main(project_assembly->get_assembly()); - CSharpLanguage::get_singleton()->lookup_scripts_in_assembly(project_assembly); - } - - return success; -} + String assembly_name = ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); -void GDMono::_install_trace_listener() { -#ifdef DEBUG_ENABLED - // Install the trace listener now before the project assembly is loaded - GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils"); - GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener"); - - 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."); + if (assembly_name.is_empty()) { + assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); } -#endif -} - -#ifndef GD_MONO_SINGLE_APPDOMAIN -Error GDMono::_load_scripts_domain() { - ERR_FAIL_COND_V(scripts_domain != nullptr, ERR_BUG); - - print_verbose("Mono: Loading scripts domain..."); - - scripts_domain = GDMonoUtils::create_domain("GodotEngine.Domain.Scripts"); - ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain."); - - mono_domain_set(scripts_domain, true); - - return OK; -} - -Error GDMono::_unload_scripts_domain() { - ERR_FAIL_NULL_V(scripts_domain, ERR_BUG); - - print_verbose("Mono: Finalizing scripts domain..."); - - if (mono_domain_get() != root_domain) { - mono_domain_set(root_domain, true); - } + String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() + .plus_file(assembly_name + ".dll"); + assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path); - finalizing_scripts_domain = true; - - if (!mono_domain_finalize(scripts_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout."); + if (!FileAccess::exists(assembly_path)) { + return false; } - finalizing_scripts_domain = false; - - mono_gc_collect(mono_gc_max_generation()); - - GDMonoCache::clear_godot_api_cache(); - - _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); - - core_api_assembly.assembly = nullptr; -#ifdef TOOLS_ENABLED - editor_api_assembly.assembly = nullptr; -#endif - - project_assembly = nullptr; -#ifdef TOOLS_ENABLED - tools_assembly = nullptr; - tools_project_editor_assembly = nullptr; -#endif - - MonoDomain *domain = scripts_domain; - scripts_domain = nullptr; - - print_verbose("Mono: Unloading scripts domain..."); + String loaded_assembly_path; + bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16(), &loaded_assembly_path); - MonoException *exc = nullptr; - mono_domain_try_unload(domain, (MonoObject **)&exc); - - if (exc) { - ERR_PRINT("Exception thrown when unloading scripts domain."); - GDMonoUtils::debug_unhandled_exception(exc); - return FAILED; + if (success) { + project_assembly_path = loaded_assembly_path.simplify_path(); + project_assembly_modified_time = FileAccess::get_modified_time(loaded_assembly_path); } - return OK; + return success; } #endif #ifdef GD_MONO_HOT_RELOAD -Error GDMono::reload_scripts_domain() { +Error GDMono::reload_project_assemblies() { ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG); - if (scripts_domain) { - Error domain_unload_err = _unload_scripts_domain(); - ERR_FAIL_COND_V_MSG(domain_unload_err != OK, domain_unload_err, "Mono: Failed to unload scripts domain."); - } - - CSharpLanguage::get_singleton()->_on_scripts_domain_unloaded(); - - Error domain_load_err = _load_scripts_domain(); - ERR_FAIL_COND_V_MSG(domain_load_err != OK, domain_load_err, "Mono: Failed to load scripts domain."); + finalizing_scripts_domain = true; - // Load assemblies. The API and tools assemblies are required, - // the application is aborted if these assemblies cannot be loaded. + CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload(); - _load_api_assemblies(); + if (!get_plugin_callbacks().UnloadProjectPluginCallback()) { + ERR_FAIL_V_MSG(Error::FAILED, ".NET: Failed to unload assemblies."); + } -#if defined(TOOLS_ENABLED) - bool tools_assemblies_loaded = _load_tools_assemblies(); - CRASH_COND_MSG(!tools_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); -#endif + finalizing_scripts_domain = false; // Load the project's main assembly. Here, during hot-reloading, we do // consider failing to load the project's main assembly to be an error. - // However, unlike the API and tools assemblies, the application can continue working. if (!_load_project_assembly()) { - print_error("Mono: Failed to load project assembly"); + print_error(".NET: Failed to load project assembly."); return ERR_CANT_OPEN; } @@ -1123,204 +558,38 @@ Error GDMono::reload_scripts_domain() { } #endif -#ifndef GD_MONO_SINGLE_APPDOMAIN -Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { - CRASH_COND(p_domain == nullptr); - CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead - - String domain_name = mono_domain_get_friendly_name(p_domain); - - print_verbose("Mono: Unloading domain '" + domain_name + "'..."); - - if (mono_domain_get() == p_domain) { - mono_domain_set(root_domain, true); - } - - if (!mono_domain_finalize(p_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout."); - } - - mono_gc_collect(mono_gc_max_generation()); - - _domain_assemblies_cleanup(mono_domain_get_id(p_domain)); - - MonoException *exc = nullptr; - mono_domain_try_unload(p_domain, (MonoObject **)&exc); - - if (exc) { - ERR_PRINT("Exception thrown when unloading domain '" + domain_name + "'."); - GDMonoUtils::debug_print_unhandled_exception(exc); - return FAILED; - } - - return OK; -} -#endif - -GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { - MonoImage *image = mono_class_get_image(p_raw_class); - - if (image == corlib_assembly->get_image()) { - return corlib_assembly->get_class(p_raw_class); - } - - int32_t domain_id = mono_domain_get_id(mono_domain_get()); - HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; - - for (const KeyValue<String, GDMonoAssembly *> &E : domain_assemblies) { - GDMonoAssembly *assembly = E.value; - if (assembly->get_image() == image) { - GDMonoClass *klass = assembly->get_class(p_raw_class); - if (klass) { - return klass; - } - } - } - - 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; - } - - int32_t domain_id = mono_domain_get_id(mono_domain_get()); - HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; - - for (const KeyValue<String, GDMonoAssembly *> &E : domain_assemblies) { - GDMonoAssembly *assembly = E.value; - klass = assembly->get_class(p_namespace, p_name); - if (klass) { - return klass; - } - } - - return nullptr; -} - -void GDMono::_domain_assemblies_cleanup(int32_t p_domain_id) { - HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id]; - - for (const KeyValue<String, GDMonoAssembly *> &E : domain_assemblies) { - memdelete(E.value); - } - - assemblies.erase(p_domain_id); -} - -void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) { - // This method will be called by the runtime when a thrown exception is not handled. - // It won't be called when we manually treat a thrown exception as unhandled. - // We assume the exception was already printed before calling this hook. - -#ifdef DEBUG_ENABLED - GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); - if (EngineDebugger::is_active()) { - EngineDebugger::get_singleton()->poll_events(false); - } -#endif - - exit(mono_environment_exitcode_get()); - - GD_UNREACHABLE(); -} - GDMono::GDMono() { singleton = this; - gdmono_log = memnew(GDMonoLog); - runtime_initialized = false; finalizing_scripts_domain = false; - root_domain = nullptr; - scripts_domain = nullptr; - - corlib_assembly = nullptr; - project_assembly = nullptr; -#ifdef TOOLS_ENABLED - tools_assembly = nullptr; - tools_project_editor_assembly = nullptr; -#endif - api_core_hash = 0; #ifdef TOOLS_ENABLED api_editor_hash = 0; #endif - - unhandled_exception_policy = POLICY_TERMINATE_APP; } 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; - - 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 - - for (const KeyValue<int32_t, HashMap<String, GDMonoAssembly *>> &E : assemblies) { - const HashMap<String, GDMonoAssembly *> &domain_assemblies = E.value; + finalizing_scripts_domain = true; - for (const KeyValue<String, GDMonoAssembly *> &F : domain_assemblies) { - memdelete(F.value); - } + if (is_runtime_initialized()) { + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.DisposablesTracker_OnGodotShuttingDown(); } - assemblies.clear(); - - print_verbose("Mono: Runtime cleanup..."); - - mono_jit_cleanup(root_domain); - - print_verbose("Mono: Finalized"); + } - runtime_initialized = false; + if (hostfxr_dll_handle) { + OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle); } + finalizing_scripts_domain = false; + runtime_initialized = false; + #if defined(ANDROID_ENABLED) gdmono::android::support::cleanup(); #endif - if (gdmono_log) { - memdelete(gdmono_log); - } - singleton = nullptr; } @@ -1328,62 +597,7 @@ namespace mono_bind { GodotSharp *GodotSharp::singleton = nullptr; -void GodotSharp::attach_thread() { - GDMonoUtils::attach_current_thread(); -} - -void GodotSharp::detach_thread() { - GDMonoUtils::detach_current_thread(); -} - -int32_t GodotSharp::get_domain_id() { - MonoDomain *domain = mono_domain_get(); - ERR_FAIL_NULL_V(domain, -1); - return mono_domain_get_id(domain); -} - -int32_t GodotSharp::get_scripts_domain_id() { - ERR_FAIL_NULL_V_MSG(GDMono::get_singleton(), - -1, "The Mono runtime is not initialized"); - MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain(); - ERR_FAIL_NULL_V(domain, -1); - return mono_domain_get_id(domain); -} - -bool GodotSharp::is_scripts_domain_loaded() { - return GDMono::get_singleton() != nullptr && - GDMono::get_singleton()->is_runtime_initialized() && - GDMono::get_singleton()->get_scripts_domain() != nullptr; -} - -bool GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { - return is_domain_finalizing_for_unload(p_domain_id); -} - -bool GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) { - return is_domain_finalizing_for_unload(mono_domain_get_by_id(p_domain_id)); -} - -bool GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { - GDMono *gd_mono = GDMono::get_singleton(); - - ERR_FAIL_COND_V_MSG(!gd_mono || !gd_mono->is_runtime_initialized(), - false, "The Mono runtime is not initialized"); - - ERR_FAIL_NULL_V(p_domain, true); - - if (p_domain == gd_mono->get_scripts_domain() && gd_mono->is_finalizing_scripts_domain()) { - return true; - } - - return mono_domain_is_unloading(p_domain); -} - -bool GodotSharp::is_runtime_shutting_down() { - return mono_runtime_is_shutting_down(); -} - -bool GodotSharp::is_runtime_initialized() { +bool GodotSharp::_is_runtime_initialized() { return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized(); } @@ -1399,16 +613,7 @@ void GodotSharp::_reload_assemblies(bool p_soft_reload) { } void GodotSharp::_bind_methods() { - ClassDB::bind_method(D_METHOD("attach_thread"), &GodotSharp::attach_thread); - ClassDB::bind_method(D_METHOD("detach_thread"), &GodotSharp::detach_thread); - - ClassDB::bind_method(D_METHOD("get_domain_id"), &GodotSharp::get_domain_id); - ClassDB::bind_method(D_METHOD("get_scripts_domain_id"), &GodotSharp::get_scripts_domain_id); - ClassDB::bind_method(D_METHOD("is_scripts_domain_loaded"), &GodotSharp::is_scripts_domain_loaded); - ClassDB::bind_method(D_METHOD("is_domain_finalizing_for_unload", "domain_id"), &GodotSharp::_is_domain_finalizing_for_unload); - - ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &GodotSharp::is_runtime_shutting_down); - ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::is_runtime_initialized); + ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::_is_runtime_initialized); ClassDB::bind_method(D_METHOD("_reload_assemblies"), &GodotSharp::_reload_assemblies); } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 51fd0f8483..43811a4325 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -34,131 +34,54 @@ #include "core/io/config_file.h" #include "../godotsharp_defs.h" -#include "gd_mono_assembly.h" -#include "gd_mono_log.h" -#ifdef WINDOWS_ENABLED -#include "../utils/mono_reg_utils.h" +#ifdef WIN32 +#define GD_CLR_STDCALL __stdcall +#else +#define GD_CLR_STDCALL #endif -namespace ApiAssemblyInfo { -enum Type { - API_CORE, - API_EDITOR -}; - -struct Version { - uint64_t godot_api_hash = 0; - uint32_t bindings_version = 0; - uint32_t cs_glue_version = 0; - - bool operator==(const Version &p_other) const { - return godot_api_hash == p_other.godot_api_hash && - bindings_version == p_other.bindings_version && - cs_glue_version == p_other.cs_glue_version; - } - - Version() {} - - Version(uint64_t p_godot_api_hash, - uint32_t p_bindings_version, - uint32_t p_cs_glue_version) : - godot_api_hash(p_godot_api_hash), - bindings_version(p_bindings_version), - cs_glue_version(p_cs_glue_version) { - } +namespace gdmono { - static Version get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, Type p_api_type); +#ifdef TOOLS_ENABLED +struct PluginCallbacks { + using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *, String *); + using FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *, const void **, int32_t); + using FuncUnloadProjectPluginCallback = bool(GD_CLR_STDCALL *)(); + FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr; + FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr; + FuncUnloadProjectPluginCallback UnloadProjectPluginCallback = nullptr; }; +#endif -String to_string(Type p_type); -} // namespace ApiAssemblyInfo - -class GDMono { -public: - enum UnhandledExceptionPolicy { - POLICY_TERMINATE_APP, - POLICY_LOG_ERROR - }; - - struct LoadedApiAssembly { - GDMonoAssembly *assembly = nullptr; - bool out_of_sync = false; +} // namespace gdmono - LoadedApiAssembly() {} - }; +#undef GD_CLR_STDCALL -private: +class GDMono { bool runtime_initialized; bool finalizing_scripts_domain; - UnhandledExceptionPolicy unhandled_exception_policy; + void *hostfxr_dll_handle = nullptr; + bool is_native_aot = false; - MonoDomain *root_domain = nullptr; - MonoDomain *scripts_domain = nullptr; + String project_assembly_path; + uint64_t project_assembly_modified_time = 0; - HashMap<int32_t, HashMap<String, GDMonoAssembly *>> assemblies; - - GDMonoAssembly *corlib_assembly = nullptr; - GDMonoAssembly *project_assembly = nullptr; #ifdef TOOLS_ENABLED - GDMonoAssembly *tools_assembly = nullptr; - GDMonoAssembly *tools_project_editor_assembly = nullptr; -#endif - - LoadedApiAssembly core_api_assembly; - LoadedApiAssembly editor_api_assembly; - - typedef bool (*CoreApiAssemblyLoadedCallback)(); - - bool _are_api_assemblies_out_of_sync(); - bool _temp_domain_load_are_assemblies_out_of_sync(const String &p_config); - - bool _load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly); -#ifdef TOOLS_ENABLED - bool _load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly); -#endif - - static bool _on_core_api_assembly_loaded(); - - bool _load_corlib_assembly(); -#ifdef TOOLS_ENABLED - bool _load_tools_assemblies(); -#endif bool _load_project_assembly(); - - bool _try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly, - const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback); - bool _try_load_api_assemblies_preset(); - void _load_api_assemblies(); - - void _install_trace_listener(); - - void _register_internal_calls(); - -#ifndef GD_MONO_SINGLE_APPDOMAIN - Error _load_scripts_domain(); - Error _unload_scripts_domain(); #endif - void _domain_assemblies_cleanup(int32_t p_domain_id); - uint64_t api_core_hash; #ifdef TOOLS_ENABLED uint64_t api_editor_hash; #endif void _init_godot_api_hashes(); - void _init_exception_policy(); - - GDMonoLog *gdmono_log = nullptr; -#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) - MonoRegInfo mono_reg_info; +#ifdef TOOLS_ENABLED + gdmono::PluginCallbacks plugin_callbacks; #endif - void add_mono_shared_libs_dir_to_path(); - void determine_mono_dirs(String &r_assembly_rootdir, String &r_config_dir); - protected: static GDMono *singleton; @@ -192,107 +115,43 @@ public: #endif } -#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 = nullptr, const bool *p_editor_api_out_of_sync = nullptr); -#endif - - static GDMono *get_singleton() { return singleton; } - - [[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(int32_t p_domain_id, GDMonoAssembly *p_assembly); - GDMonoAssembly *get_loaded_assembly(const String &p_name); - - _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; } + static GDMono *get_singleton() { + return singleton; + } - _FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; } + _FORCE_INLINE_ bool is_runtime_initialized() const { + return runtime_initialized; + } + _FORCE_INLINE_ bool is_finalizing_scripts_domain() { + return finalizing_scripts_domain; + } - _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } + _FORCE_INLINE_ const String &get_project_assembly_path() const { + return project_assembly_path; + } + _FORCE_INLINE_ uint64_t get_project_assembly_modified_time() const { + return project_assembly_modified_time; + } - _FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly.assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; } #ifdef TOOLS_ENABLED - _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly.assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; } -#endif - -#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) - const MonoRegInfo &get_mono_reg_info() { return mono_reg_info; } + const gdmono::PluginCallbacks &get_plugin_callbacks() { + return plugin_callbacks; + } #endif - GDMonoClass *get_class(MonoClass *p_raw_class); - GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); - #ifdef GD_MONO_HOT_RELOAD - Error reload_scripts_domain(); + Error reload_project_assemblies(); #endif - bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false); - bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false); - bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs); - bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false); - - Error finalize_and_unload_domain(MonoDomain *p_domain); - void initialize(); +#ifdef TOOLS_ENABLED void initialize_load_assemblies(); +#endif GDMono(); ~GDMono(); }; -namespace gdmono { - -class ScopeDomain { - MonoDomain *prev_domain = nullptr; - -public: - ScopeDomain(MonoDomain *p_domain) { - prev_domain = mono_domain_get(); - if (prev_domain != p_domain) { - mono_domain_set(p_domain, false); - } else { - prev_domain = nullptr; - } - } - - ~ScopeDomain() { - if (prev_domain) { - mono_domain_set(prev_domain, false); - } - } -}; - -class ScopeExitDomainUnload { - MonoDomain *domain = nullptr; - -public: - ScopeExitDomainUnload(MonoDomain *p_domain) : - domain(p_domain) { - } - - ~ScopeExitDomainUnload() { - if (domain) { - GDMono::get_singleton()->finalize_and_unload_domain(domain); - } - } -}; -} // namespace gdmono - -#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \ - gdmono::ScopeDomain __gdmono__scope__domain__(m_mono_domain); \ - (void)__gdmono__scope__domain__; - -#define _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(m_mono_domain) \ - gdmono::ScopeExitDomainUnload __gdmono__scope__exit__domain__unload__(m_mono_domain); \ - (void)__gdmono__scope__exit__domain__unload__; - namespace mono_bind { class GodotSharp : public Object { @@ -300,9 +159,8 @@ class GodotSharp : public Object { friend class GDMono; - bool _is_domain_finalizing_for_unload(int32_t p_domain_id); - void _reload_assemblies(bool p_soft_reload); + bool _is_runtime_initialized(); protected: static GodotSharp *singleton; @@ -311,20 +169,6 @@ protected: public: static GodotSharp *get_singleton() { return singleton; } - void attach_thread(); - void detach_thread(); - - int32_t get_domain_id(); - int32_t get_scripts_domain_id(); - - bool is_scripts_domain_loaded(); - - bool is_domain_finalizing_for_unload(int32_t p_domain_id); - bool is_domain_finalizing_for_unload(MonoDomain *p_domain); - - bool is_runtime_shutting_down(); - bool is_runtime_initialized(); - GodotSharp(); ~GodotSharp(); }; diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp deleted file mode 100644 index 42c6b6305f..0000000000 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ /dev/null @@ -1,482 +0,0 @@ -/*************************************************************************/ -/* gd_mono_assembly.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_assembly.h" - -#include <mono/metadata/mono-debug.h> -#include <mono/metadata/tokentype.h> - -#include "core/config/project_settings.h" -#include "core/io/file_access.h" -#include "core/io/file_access_pack.h" -#include "core/os/os.h" -#include "core/templates/list.h" - -#include "../godotsharp_dirs.h" -#include "gd_mono_cache.h" -#include "gd_mono_class.h" - -Vector<String> GDMonoAssembly::search_dirs; - -void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) { - String framework_dir; - - if (!p_custom_bcl_dir.is_empty()) { - framework_dir = p_custom_bcl_dir; - } else if (mono_assembly_getrootdir()) { - framework_dir = String::utf8(mono_assembly_getrootdir()).plus_file("mono").plus_file("4.5"); - } - - if (!framework_dir.is_empty()) { - r_search_dirs.push_back(framework_dir); - r_search_dirs.push_back(framework_dir.plus_file("Facades")); - } - -#if !defined(TOOLS_ENABLED) - String data_game_assemblies_dir = GodotSharpDirs::get_data_game_assemblies_dir(); - if (!data_game_assemblies_dir.is_empty()) { - r_search_dirs.push_back(data_game_assemblies_dir); - } -#endif - - if (p_custom_config.length()) { - r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(p_custom_config)); - } else { - r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); - } - - if (p_custom_config.is_empty()) { - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); - } else { - String api_config = p_custom_config == "ExportRelease" ? "Release" : "Debug"; - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config)); - } - - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir()); - r_search_dirs.push_back(OS::get_singleton()->get_resource_dir()); - r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir()); - -#ifdef TOOLS_ENABLED - r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir()); - - // For GodotTools to find the api assemblies - r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug")); -#endif -} - -// 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, [[maybe_unused]] void *user_data) { - String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); - - MonoImage *image = mono_assembly_get_image(assembly); - - GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, image, assembly)); - -#ifdef GD_MONO_HOT_RELOAD - String path = String::utf8(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) { - return GDMonoAssembly::_search_hook(aname, user_data, false); -} - -MonoAssembly *GDMonoAssembly::assembly_refonly_search_hook(MonoAssemblyName *aname, void *user_data) { - return GDMonoAssembly::_search_hook(aname, user_data, true); -} - -MonoAssembly *GDMonoAssembly::assembly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) { - return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, false); -} - -MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) { - return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true); -} - -MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) { - String name = String::utf8(mono_assembly_name_get_name(aname)); - bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); - - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); - if (loaded_asm) { - return loaded_asm->get_assembly(); - } - - return nullptr; -} - -MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) { - String name = String::utf8(mono_assembly_name_get_name(aname)); - return _load_assembly_search(name, aname, refonly, search_dirs); -} - -MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) { - MonoAssembly *res = nullptr; - String path; - - bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe"); - - for (int i = 0; i < p_search_dirs.size(); i++) { - const String &search_dir = p_search_dirs[i]; - - if (has_extension) { - path = search_dir.plus_file(p_name); - if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly, p_aname); - if (res != nullptr) { - return res; - } - } - } else { - path = search_dir.plus_file(p_name + ".dll"); - if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly, p_aname); - if (res != nullptr) { - return res; - } - } - - path = search_dir.plus_file(p_name + ".exe"); - if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly, p_aname); - if (res != nullptr) { - return res; - } - } - } - } - - return nullptr; -} - -String GDMonoAssembly::find_assembly(const String &p_name) { - String path; - - bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe"); - - for (int i = 0; i < search_dirs.size(); i++) { - const String &search_dir = search_dirs[i]; - - if (has_extension) { - path = search_dir.plus_file(p_name); - if (FileAccess::exists(path)) { - return path; - } - } else { - path = search_dir.plus_file(p_name + ".dll"); - if (FileAccess::exists(path)) { - return path; - } - - path = search_dir.plus_file(p_name + ".exe"); - if (FileAccess::exists(path)) { - return path; - } - } - } - - return String(); -} - -void GDMonoAssembly::initialize() { - fill_search_dirs(search_dirs); - - 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); -} - -MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) { - Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); - ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, "Could read the assembly in the specified location"); - - String image_filename; - -#ifdef ANDROID_ENABLED - if (p_path.begins_with("res://")) { - image_filename = p_path.substr(6, p_path.length()); - } else { - 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(p_path); -#endif - - MonoImageOpenStatus status = MONO_IMAGE_OK; - - MonoImage *image = mono_image_open_from_data_with_name( - (char *)&data[0], data.size(), - true, &status, p_refonly, - image_filename.utf8()); - - ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'."); - - if (p_aname != nullptr) { - // Check assembly version - const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY); - - ERR_FAIL_NULL_V(table, nullptr); - - if (mono_table_info_get_rows(table)) { - uint32_t cols[MONO_ASSEMBLY_SIZE]; - mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE); - - // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision. - uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION]; - uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION]; - - uint16_t required_minor; - uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr); - - if (required_major != 0) { - if (major != required_major && minor != required_minor) { - mono_image_close(image); - return nullptr; - } - } - } - } - -#ifdef DEBUG_ENABLED - Vector<uint8_t> pdb_data; - String pdb_path(p_path + ".pdb"); - - if (!FileAccess::exists(pdb_path)) { - pdb_path = p_path.get_basename() + ".pdb"; // without .dll - - if (!FileAccess::exists(pdb_path)) { - goto no_pdb; - } - } - - pdb_data = FileAccess::get_file_as_array(pdb_path); - - // mono_debug_close_image doesn't seem to be needed - mono_debug_open_image_from_memory(image, &pdb_data[0], pdb_data.size()); - -no_pdb: - -#endif - - bool need_manual_load_hook = mono_image_get_assembly(image) != nullptr; // Re-using an existing image with an assembly loaded - - status = MONO_IMAGE_OK; - - MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly); - - ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, nullptr, "Failed to load assembly for image"); - - if (need_manual_load_hook) { - // For some reason if an assembly survived domain reloading (maybe because it's referenced somewhere else), - // the mono internal search hook don't detect it, yet mono_image_open_from_data_with_name re-uses the image - // and assembly, and mono_assembly_load_from_full doesn't call the load hook. We need to call it manually. - String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); - bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); - if (!loaded_asm) { - assembly_load_hook(assembly, nullptr); - } - } - - // Decrement refcount which was previously incremented by mono_image_open_from_data_with_name - mono_image_close(image); - - return assembly; -} - -void GDMonoAssembly::unload() { - ERR_FAIL_NULL(image); // Should not be called if already unloaded - - for (const KeyValue<MonoClass *, GDMonoClass *> &E : cached_raw) { - memdelete(E.value); - } - - cached_classes.clear(); - cached_raw.clear(); - - assembly = nullptr; - image = nullptr; -} - -String GDMonoAssembly::get_path() const { - return String::utf8(mono_image_get_filename(image)); -} - -bool GDMonoAssembly::has_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, false); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoAssembly::get_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, nullptr); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoAssembly::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - - attributes = mono_custom_attrs_from_assembly(assembly); - attrs_fetched = true; -} - -GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) { - ERR_FAIL_NULL_V(image, nullptr); - - ClassKey key(p_namespace, p_name); - - GDMonoClass **match = cached_classes.getptr(key); - - if (match) { - return *match; - } - - MonoClass *mono_class = mono_class_from_name(image, String(p_namespace).utf8(), String(p_name).utf8()); - - if (!mono_class) { - return nullptr; - } - - GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this)); - - cached_classes[key] = wrapped_class; - cached_raw[mono_class] = wrapped_class; - - return wrapped_class; -} - -GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) { - ERR_FAIL_NULL_V(image, nullptr); - - HashMap<MonoClass *, GDMonoClass *>::Iterator match = cached_raw.find(p_mono_class); - - if (match) { - return match->value; - } - - StringName namespace_name = String::utf8(mono_class_get_namespace(p_mono_class)); - StringName class_name = String::utf8(mono_class_get_name(p_mono_class)); - - GDMonoClass *wrapped_class = memnew(GDMonoClass(namespace_name, class_name, p_mono_class, this)); - - cached_classes[ClassKey(namespace_name, class_name)] = wrapped_class; - cached_raw[p_mono_class] = wrapped_class; - - return wrapped_class; -} - -GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) { - if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) { - return GDMono::get_singleton()->get_corlib_assembly(); - } - - // We need to manually call the search hook in this case, as it won't be called in the next step - MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname); - - if (!assembly) { - assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs); - if (!assembly) { - return nullptr; - } - } - - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); - ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?"); - ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr); - - return loaded_asm; -} - -GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) { - if (p_name == "mscorlib" || p_name == "mscorlib.dll") { - return GDMono::get_singleton()->get_corlib_assembly(); - } - - // We need to manually call the search hook in this case, as it won't be called in the next step - MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); - 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); - if (!assembly) { - return nullptr; - } - } - - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); - ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?"); - - return loaded_asm; -} - -GDMonoAssembly::~GDMonoAssembly() { - if (image) { - unload(); - } -} diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h deleted file mode 100644 index 0a3ae6c4fe..0000000000 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ /dev/null @@ -1,138 +0,0 @@ -/*************************************************************************/ -/* gd_mono_assembly.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_ASSEMBLY_H -#define GD_MONO_ASSEMBLY_H - -#include <mono/jit/jit.h> -#include <mono/metadata/assembly.h> - -#include "core/string/ustring.h" -#include "core/templates/hash_map.h" -#include "core/templates/rb_map.h" -#include "gd_mono_utils.h" - -class GDMonoAssembly { - struct ClassKey { - struct Hasher { - static _FORCE_INLINE_ uint32_t hash(const ClassKey &p_key) { - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.namespace_name.hash()); - GDMonoUtils::hash_combine(hash, p_key.class_name.hash()); - - return hash; - } - }; - - _FORCE_INLINE_ bool operator==(const ClassKey &p_a) const { - return p_a.class_name == class_name && p_a.namespace_name == namespace_name; - } - - ClassKey() {} - - ClassKey(const StringName &p_namespace_name, const StringName &p_class_name) { - namespace_name = p_namespace_name; - class_name = p_class_name; - } - - StringName namespace_name; - StringName class_name; - }; - - String name; - MonoImage *image = nullptr; - MonoAssembly *assembly = nullptr; - - bool attrs_fetched = false; - MonoCustomAttrInfo *attributes = nullptr; - -#ifdef GD_MONO_HOT_RELOAD - uint64_t modified_time = 0; -#endif - - HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes; - HashMap<MonoClass *, GDMonoClass *> cached_raw; - - static Vector<String> search_dirs; - - static void assembly_load_hook(MonoAssembly *assembly, void *user_data); - static MonoAssembly *assembly_search_hook(MonoAssemblyName *aname, void *user_data); - static MonoAssembly *assembly_refonly_search_hook(MonoAssemblyName *aname, void *user_data); - static MonoAssembly *assembly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data); - static MonoAssembly *assembly_refonly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data); - - static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly); - static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly); - - static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr); - static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs); - - friend class GDMono; - static void initialize(); - -public: - void unload(); - - _FORCE_INLINE_ MonoImage *get_image() const { return image; } - _FORCE_INLINE_ MonoAssembly *get_assembly() const { return assembly; } - _FORCE_INLINE_ String get_name() const { return name; } - -#ifdef GD_MONO_HOT_RELOAD - _FORCE_INLINE_ uint64_t get_modified_time() const { return modified_time; } -#endif - - String get_path() const; - - bool has_attribute(GDMonoClass *p_attr_class); - MonoObject *get_attribute(GDMonoClass *p_attr_class); - - void fetch_attributes(); - - GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); - GDMonoClass *get_class(MonoClass *p_mono_class); - - static String find_assembly(const String &p_name); - - static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); - static const Vector<String> &get_default_search_dirs() { return search_dirs; } - - static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs); - static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); - - GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) : - name(p_name), - image(p_image), - assembly(p_assembly) { - } - ~GDMonoAssembly(); -}; - -#endif // GD_MONO_ASSEMBLY_H diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 69d8c7edc9..bfd803f326 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -30,310 +30,66 @@ #include "gd_mono_cache.h" -#include "gd_mono.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_method.h" -#include "gd_mono_utils.h" +#include "core/error/error_macros.h" namespace GDMonoCache { -CachedData cached_data; +ManagedCallbacks managed_callbacks; +bool godot_api_cache_updated = false; -#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) -#define CACHE_NS_CLASS_AND_CHECK(m_ns, m_class, m_val) CACHE_AND_CHECK(cached_data.class_##m_ns##_##m_class, m_val) -#define CACHE_RAW_MONO_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(cached_data.rawclass_##m_class, m_val) -#define CACHE_FIELD_AND_CHECK(m_class, m_field, m_val) CACHE_AND_CHECK(cached_data.field_##m_class##_##m_field, m_val) -#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) +void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { + int checked_count = 0; -#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 CHECK_CALLBACK_NOT_NULL_IMPL(m_var, m_class, m_method) \ + { \ + ERR_FAIL_COND_MSG(m_var == nullptr, \ + "Mono Cache: Managed callback for '" #m_class "_" #m_method "' is null."); \ + checked_count += 1; \ } -#define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_METHOD_THUNK_AND_CHECK_IMPL(cached_data.methodthunk_##m_class##_##m_method, m_val) - -void CachedData::clear_corlib_cache() { - corlib_cache_updated = false; - - class_MonoObject = nullptr; - 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_ICollection = nullptr; - class_System_Collections_IDictionary = nullptr; - -#ifdef DEBUG_ENABLED - class_System_Diagnostics_StackTrace = nullptr; - methodthunk_System_Diagnostics_StackTrace_GetFrames.nullify(); - method_System_Diagnostics_StackTrace_ctor_bool = nullptr; - method_System_Diagnostics_StackTrace_ctor_Exception_bool = nullptr; -#endif - - class_KeyNotFoundException = nullptr; -} - -void CachedData::clear_godot_api_cache() { - godot_api_cache_updated = false; - - 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_Vector4 = nullptr; - class_Vector4i = nullptr; - class_Basis = nullptr; - class_Quaternion = nullptr; - class_Transform3D = nullptr; - class_Projection = 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 = nullptr; - methodthunk_DebuggingUtils_GetStackFrameInfo.nullify(); -#endif - - class_ExportAttribute = nullptr; - field_ExportAttribute_hint = nullptr; - field_ExportAttribute_hintString = nullptr; - class_SignalAttribute = nullptr; - class_ToolAttribute = nullptr; - class_RPCAttribute = nullptr; - property_RPCAttribute_Mode = nullptr; - property_RPCAttribute_CallLocal = nullptr; - property_RPCAttribute_TransferMode = nullptr; - property_RPCAttribute_TransferChannel = nullptr; - class_GodotMethodAttribute = nullptr; - field_GodotMethodAttribute_methodName = nullptr; - class_ScriptPathAttribute = nullptr; - field_ScriptPathAttribute_path = nullptr; - class_AssemblyHasScriptsAttribute = nullptr; - field_AssemblyHasScriptsAttribute_requiresLookup = nullptr; - field_AssemblyHasScriptsAttribute_scriptTypes = 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_GodotTaskScheduler_Activate.nullify(); - - methodthunk_Delegate_Equals.nullify(); - - methodthunk_DelegateUtils_TrySerializeDelegate.nullify(); - methodthunk_DelegateUtils_TryDeserializeDelegate.nullify(); - - // Start of MarshalUtils methods - - methodthunk_MarshalUtils_TypeIsGenericArray.nullify(); - methodthunk_MarshalUtils_TypeIsGenericDictionary.nullify(); - methodthunk_MarshalUtils_TypeIsSystemGenericList.nullify(); - methodthunk_MarshalUtils_TypeIsSystemGenericDictionary.nullify(); - methodthunk_MarshalUtils_TypeIsGenericIEnumerable.nullify(); - methodthunk_MarshalUtils_TypeIsGenericICollection.nullify(); - methodthunk_MarshalUtils_TypeIsGenericIDictionary.nullify(); - methodthunk_MarshalUtils_TypeHasFlagsAttribute.nullify(); - - methodthunk_MarshalUtils_GetGenericTypeDefinition.nullify(); - - methodthunk_MarshalUtils_ArrayGetElementType.nullify(); - methodthunk_MarshalUtils_DictionaryGetKeyValueTypes.nullify(); - - methodthunk_MarshalUtils_MakeGenericArrayType.nullify(); - methodthunk_MarshalUtils_MakeGenericDictionaryType.nullify(); - - // End of MarshalUtils methods - - task_scheduler_handle = Ref<MonoGCHandleRef>(); -} - -#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) -#define GODOT_API_NS_CLASS(m_ns, m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(m_ns, #m_class)) - -void update_corlib_cache() { - CACHE_CLASS_AND_CHECK(MonoObject, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_object_class())); - CACHE_CLASS_AND_CHECK(bool, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_boolean_class())); - CACHE_CLASS_AND_CHECK(int8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_sbyte_class())); - CACHE_CLASS_AND_CHECK(int16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int16_class())); - CACHE_CLASS_AND_CHECK(int32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int32_class())); - CACHE_CLASS_AND_CHECK(int64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int64_class())); - CACHE_CLASS_AND_CHECK(uint8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_byte_class())); - CACHE_CLASS_AND_CHECK(uint16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint16_class())); - CACHE_CLASS_AND_CHECK(uint32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint32_class())); - CACHE_CLASS_AND_CHECK(uint64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint64_class())); - CACHE_CLASS_AND_CHECK(float, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_single_class())); - CACHE_CLASS_AND_CHECK(double, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_double_class())); - CACHE_CLASS_AND_CHECK(String, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_string_class())); - CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class())); - - CACHE_CLASS_AND_CHECK(System_Collections_IEnumerable, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IEnumerable")); - CACHE_CLASS_AND_CHECK(System_Collections_ICollection, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "ICollection")); - CACHE_CLASS_AND_CHECK(System_Collections_IDictionary, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IDictionary")); - -#ifdef DEBUG_ENABLED - CACHE_CLASS_AND_CHECK(System_Diagnostics_StackTrace, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Diagnostics", "StackTrace")); - CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method("GetFrames")); - CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(bool)", true)); - 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)", true)); - - CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException")); - - cached_data.corlib_cache_updated = true; -} - -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(Vector4, GODOT_API_CLASS(Vector4)); - CACHE_CLASS_AND_CHECK(Vector4i, GODOT_API_CLASS(Vector4i)); - CACHE_CLASS_AND_CHECK(Basis, GODOT_API_CLASS(Basis)); - CACHE_CLASS_AND_CHECK(Quaternion, GODOT_API_CLASS(Quaternion)); - CACHE_CLASS_AND_CHECK(Transform3D, GODOT_API_CLASS(Transform3D)); - CACHE_CLASS_AND_CHECK(Projection, GODOT_API_CLASS(Projection)); - 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(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)); - CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener)); - -#ifdef DEBUG_ENABLED - CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils)); -#endif - - // Attributes - CACHE_CLASS_AND_CHECK(ExportAttribute, GODOT_API_CLASS(ExportAttribute)); - CACHE_FIELD_AND_CHECK(ExportAttribute, hint, CACHED_CLASS(ExportAttribute)->get_field("hint")); - CACHE_FIELD_AND_CHECK(ExportAttribute, hintString, CACHED_CLASS(ExportAttribute)->get_field("hintString")); - CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute)); - CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute)); - CACHE_CLASS_AND_CHECK(RPCAttribute, GODOT_API_CLASS(RPCAttribute)); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, Mode, CACHED_CLASS(RPCAttribute)->get_property("Mode")); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, CallLocal, CACHED_CLASS(RPCAttribute)->get_property("CallLocal")); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, TransferMode, CACHED_CLASS(RPCAttribute)->get_property("TransferMode")); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, TransferChannel, CACHED_CLASS(RPCAttribute)->get_property("TransferChannel")); - CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); - CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); - CACHE_CLASS_AND_CHECK(ScriptPathAttribute, GODOT_API_CLASS(ScriptPathAttribute)); - CACHE_FIELD_AND_CHECK(ScriptPathAttribute, path, CACHED_CLASS(ScriptPathAttribute)->get_field("path")); - CACHE_CLASS_AND_CHECK(AssemblyHasScriptsAttribute, GODOT_API_CLASS(AssemblyHasScriptsAttribute)); - CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, requiresLookup, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("requiresLookup")); - CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, scriptTypes, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("scriptTypes")); - - 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)); - - CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, CACHED_CLASS(GodotObject)->get_method("Dispose", 0)); - 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(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)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericDictionary", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsSystemGenericList, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsSystemGenericList", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsSystemGenericDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsSystemGenericDictionary", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericIEnumerable, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericIEnumerable", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericICollection, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericICollection", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericIDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericIDictionary", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeHasFlagsAttribute, GODOT_API_CLASS(MarshalUtils)->get_method("TypeHasFlagsAttribute", 1)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GetGenericTypeDefinition, GODOT_API_CLASS(MarshalUtils)->get_method("GetGenericTypeDefinition", 2)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArrayGetElementType, GODOT_API_CLASS(MarshalUtils)->get_method("ArrayGetElementType", 2)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryGetKeyValueTypes, GODOT_API_CLASS(MarshalUtils)->get_method("DictionaryGetKeyValueTypes", 3)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericArrayType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericArrayType", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericDictionaryType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericDictionaryType", 2)); - - // End of MarshalUtils methods - -#ifdef DEBUG_ENABLED - CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, GODOT_API_CLASS(DebuggingUtils)->get_method("GetStackFrameInfo", 4)); -#endif - - // 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 = MonoGCHandleRef::create_strong(task_scheduler); +#define CHECK_CALLBACK_NOT_NULL(m_class, m_method) CHECK_CALLBACK_NOT_NULL_IMPL(p_managed_callbacks.m_class##_##m_method, m_class, m_method) + + CHECK_CALLBACK_NOT_NULL(SignalAwaiter, SignalCallback); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateEquals); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, TrySerializeDelegateWithGCHandle); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, TryDeserializeDelegateWithGCHandle); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, FrameCallback); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectScriptInstance); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetScriptNativeName); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SetGodotObjectPtr); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RaiseEventSignal); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, ScriptIsOrInherits); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, AddScriptBridge); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, TryReloadRegisteredScriptWithClass); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, UpdateScriptClassInfo); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyDefaultValues); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Call); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Set); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallDispose); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, HasMethodUnknownParams); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, SerializeState); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, DeserializeState); + CHECK_CALLBACK_NOT_NULL(GCHandleBridge, FreeGCHandle); + CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo); + CHECK_CALLBACK_NOT_NULL(DisposablesTracker, OnGodotShuttingDown); + CHECK_CALLBACK_NOT_NULL(GD, OnCoreApiAssemblyLoaded); + + managed_callbacks = p_managed_callbacks; + + // It's easy to forget to add new callbacks here, so this should help + if (checked_count * sizeof(void *) != sizeof(ManagedCallbacks)) { + int missing_count = (sizeof(ManagedCallbacks) / sizeof(void *)) - checked_count; + WARN_PRINT("The presence of " + itos(missing_count) + " callback(s) was not validated"); + } - cached_data.godot_api_cache_updated = true; + godot_api_cache_updated = true; } } // namespace GDMonoCache diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index e9cc26899e..ca3a6c95a7 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -31,174 +31,120 @@ #ifndef GD_MONO_CACHE_H #define GD_MONO_CACHE_H -#include "gd_mono_header.h" -#include "gd_mono_method_thunk.h" +#include <stdint.h> + +#include "../csharp_script.h" +#include "../interop_types.h" +#include "../mono_gc_handle.h" +#include "core/object/object.h" +#include "core/string/string_name.h" +#include "core/string/ustring.h" +#include "core/variant/callable.h" +#include "core/variant/dictionary.h" +#include "core/variant/variant.h" + +class CSharpScript; namespace GDMonoCache { -struct CachedData { - // ----------------------------------------------- - // corlib classes - - // Let's use the no-namespace format for these too - GDMonoClass *class_MonoObject = nullptr; // object - GDMonoClass *class_bool = nullptr; // bool - GDMonoClass *class_int8_t = nullptr; // sbyte - GDMonoClass *class_int16_t = nullptr; // short - GDMonoClass *class_int32_t = nullptr; // int - GDMonoClass *class_int64_t = nullptr; // long - GDMonoClass *class_uint8_t = nullptr; // byte - GDMonoClass *class_uint16_t = nullptr; // ushort - GDMonoClass *class_uint32_t = nullptr; // uint - GDMonoClass *class_uint64_t = nullptr; // ulong - GDMonoClass *class_float = nullptr; // float - GDMonoClass *class_double = nullptr; // double - GDMonoClass *class_String = nullptr; // string - GDMonoClass *class_IntPtr = nullptr; // System.IntPtr - - GDMonoClass *class_System_Collections_IEnumerable = nullptr; - GDMonoClass *class_System_Collections_ICollection = nullptr; - GDMonoClass *class_System_Collections_IDictionary = nullptr; - -#ifdef DEBUG_ENABLED - GDMonoClass *class_System_Diagnostics_StackTrace = nullptr; - GDMonoMethodThunkR<MonoArray *, MonoObject *> methodthunk_System_Diagnostics_StackTrace_GetFrames; - GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_bool = nullptr; - GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool = nullptr; +#ifdef WIN32 +#define GD_CLR_STDCALL __stdcall +#else +#define GD_CLR_STDCALL #endif - GDMonoClass *class_KeyNotFoundException = nullptr; - - MonoClass *rawclass_Dictionary = nullptr; - // ----------------------------------------------- - - GDMonoClass *class_Vector2 = nullptr; - GDMonoClass *class_Vector2i = nullptr; - GDMonoClass *class_Rect2 = nullptr; - GDMonoClass *class_Rect2i = nullptr; - GDMonoClass *class_Transform2D = nullptr; - GDMonoClass *class_Vector3 = nullptr; - GDMonoClass *class_Vector3i = nullptr; - GDMonoClass *class_Vector4 = nullptr; - GDMonoClass *class_Vector4i = nullptr; - GDMonoClass *class_Basis = nullptr; - GDMonoClass *class_Quaternion = nullptr; - GDMonoClass *class_Transform3D = nullptr; - GDMonoClass *class_Projection = nullptr; - GDMonoClass *class_AABB = nullptr; - GDMonoClass *class_Color = nullptr; - GDMonoClass *class_Plane = nullptr; - GDMonoClass *class_StringName = nullptr; - GDMonoClass *class_NodePath = nullptr; - GDMonoClass *class_RID = nullptr; - GDMonoClass *class_GodotObject = nullptr; - GDMonoClass *class_GodotResource = nullptr; - GDMonoClass *class_Node = nullptr; - GDMonoClass *class_Control = nullptr; - GDMonoClass *class_Node3D = nullptr; - GDMonoClass *class_WeakRef = nullptr; - GDMonoClass *class_Callable = nullptr; - GDMonoClass *class_SignalInfo = nullptr; - GDMonoClass *class_Array = nullptr; - GDMonoClass *class_Dictionary = nullptr; - GDMonoClass *class_MarshalUtils = nullptr; - GDMonoClass *class_ISerializationListener = nullptr; - -#ifdef DEBUG_ENABLED - GDMonoClass *class_DebuggingUtils = nullptr; - GDMonoMethodThunk<MonoObject *, MonoString **, int *, MonoString **> methodthunk_DebuggingUtils_GetStackFrameInfo; -#endif +struct godotsharp_property_info { + godot_string_name name; // Not owned + godot_string hint_string; + Variant::Type type; + PropertyHint hint; + PropertyUsageFlags usage; + bool exported; +}; - GDMonoClass *class_ExportAttribute = nullptr; - GDMonoField *field_ExportAttribute_hint = nullptr; - GDMonoField *field_ExportAttribute_hintString = nullptr; - GDMonoClass *class_SignalAttribute = nullptr; - GDMonoClass *class_ToolAttribute = nullptr; - GDMonoClass *class_RPCAttribute = nullptr; - GDMonoProperty *property_RPCAttribute_Mode = nullptr; - GDMonoProperty *property_RPCAttribute_CallLocal = nullptr; - GDMonoProperty *property_RPCAttribute_TransferMode = nullptr; - GDMonoProperty *property_RPCAttribute_TransferChannel = nullptr; - GDMonoClass *class_GodotMethodAttribute = nullptr; - GDMonoField *field_GodotMethodAttribute_methodName = nullptr; - GDMonoClass *class_ScriptPathAttribute = nullptr; - GDMonoField *field_ScriptPathAttribute_path = nullptr; - GDMonoClass *class_AssemblyHasScriptsAttribute = nullptr; - GDMonoField *field_AssemblyHasScriptsAttribute_requiresLookup = nullptr; - GDMonoField *field_AssemblyHasScriptsAttribute_scriptTypes = nullptr; - - GDMonoField *field_GodotObject_ptr = nullptr; - GDMonoField *field_StringName_ptr = nullptr; - GDMonoField *field_NodePath_ptr = nullptr; - GDMonoField *field_Image_ptr = nullptr; - GDMonoField *field_RID_ptr = nullptr; - - GDMonoMethodThunk<MonoObject *> methodthunk_GodotObject_Dispose; - GDMonoMethodThunkR<Array *, MonoObject *> methodthunk_Array_GetPtr; - GDMonoMethodThunkR<Dictionary *, MonoObject *> methodthunk_Dictionary_GetPtr; - GDMonoMethodThunk<MonoObject *, MonoArray *> methodthunk_SignalAwaiter_SignalCallback; - 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; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericDictionary; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsSystemGenericList; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsSystemGenericDictionary; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericIEnumerable; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericICollection; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericIDictionary; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeHasFlagsAttribute; - - GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_GetGenericTypeDefinition; - - GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_ArrayGetElementType; - GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **, MonoReflectionType **> methodthunk_MarshalUtils_DictionaryGetKeyValueTypes; - - GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericArrayType; - GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericDictionaryType; - - // End of MarshalUtils methods - - Ref<MonoGCHandleRef> task_scheduler_handle; - - bool corlib_cache_updated; - bool godot_api_cache_updated; - - void clear_corlib_cache(); - void clear_godot_api_cache(); - - CachedData() { - clear_corlib_cache(); - clear_godot_api_cache(); - } +struct godotsharp_property_def_val_pair { + godot_string_name name; // Not owned + godot_variant value; }; -extern CachedData cached_data; +struct ManagedCallbacks { + using Callback_ScriptManagerBridge_GetPropertyInfoList_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, const String *, godotsharp_property_info *p_props, int32_t p_count); + using Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, godotsharp_property_def_val_pair *p_def_vals, int32_t p_count); + + using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *); + using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *); + using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr); + using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *); + using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *); + using FuncScriptManagerBridge_FrameCallback = void(GD_CLR_STDCALL *)(); + using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *); + using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t); + using FuncScriptManagerBridge_GetScriptNativeName = void(GD_CLR_STDCALL *)(const CSharpScript *, StringName *); + using FuncScriptManagerBridge_SetGodotObjectPtr = void(GD_CLR_STDCALL *)(GCHandleIntPtr, Object *); + using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, bool *); + using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *); + using FuncScriptManagerBridge_AddScriptBridge = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *); + using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *); + using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *); + using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *); + using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *); + using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool); + using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add); + using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add); + using FuncCSharpInstanceBridge_Call = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *); + using FuncCSharpInstanceBridge_Set = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant *); + using FuncCSharpInstanceBridge_Get = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, Variant *); + using FuncCSharpInstanceBridge_CallDispose = void(GD_CLR_STDCALL *)(GCHandleIntPtr, bool); + using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *); + using FuncCSharpInstanceBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *); + using FuncCSharpInstanceBridge_SerializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *); + using FuncCSharpInstanceBridge_DeserializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *); + using FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr); + using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *); + using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)(); + using FuncGD_OnCoreApiAssemblyLoaded = void(GD_CLR_STDCALL *)(bool); + + FuncSignalAwaiter_SignalCallback SignalAwaiter_SignalCallback; + FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs; + FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals; + FuncDelegateUtils_TrySerializeDelegateWithGCHandle DelegateUtils_TrySerializeDelegateWithGCHandle; + FuncDelegateUtils_TryDeserializeDelegateWithGCHandle DelegateUtils_TryDeserializeDelegateWithGCHandle; + FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback; + FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding; + FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance; + FuncScriptManagerBridge_GetScriptNativeName ScriptManagerBridge_GetScriptNativeName; + FuncScriptManagerBridge_SetGodotObjectPtr ScriptManagerBridge_SetGodotObjectPtr; + FuncScriptManagerBridge_RaiseEventSignal ScriptManagerBridge_RaiseEventSignal; + FuncScriptManagerBridge_ScriptIsOrInherits ScriptManagerBridge_ScriptIsOrInherits; + FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge; + FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath ScriptManagerBridge_GetOrCreateScriptBridgeForPath; + FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge; + FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass ScriptManagerBridge_TryReloadRegisteredScriptWithClass; + FuncScriptManagerBridge_UpdateScriptClassInfo ScriptManagerBridge_UpdateScriptClassInfo; + FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType; + FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList; + FuncScriptManagerBridge_GetPropertyDefaultValues ScriptManagerBridge_GetPropertyDefaultValues; + FuncCSharpInstanceBridge_Call CSharpInstanceBridge_Call; + FuncCSharpInstanceBridge_Set CSharpInstanceBridge_Set; + FuncCSharpInstanceBridge_Get CSharpInstanceBridge_Get; + FuncCSharpInstanceBridge_CallDispose CSharpInstanceBridge_CallDispose; + FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString; + FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams; + FuncCSharpInstanceBridge_SerializeState CSharpInstanceBridge_SerializeState; + FuncCSharpInstanceBridge_DeserializeState CSharpInstanceBridge_DeserializeState; + FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle; + FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo; + FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown; + FuncGD_OnCoreApiAssemblyLoaded GD_OnCoreApiAssemblyLoaded; +}; -void update_corlib_cache(); -void update_godot_api_cache(); +extern ManagedCallbacks managed_callbacks; +extern bool godot_api_cache_updated; -inline void clear_corlib_cache() { - cached_data.clear_corlib_cache(); -} +void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks); -inline void clear_godot_api_cache() { - cached_data.clear_godot_api_cache(); -} } // namespace GDMonoCache -#define CACHED_CLASS(m_class) (GDMonoCache::cached_data.class_##m_class) -#define CACHED_CLASS_RAW(m_class) (GDMonoCache::cached_data.class_##m_class->get_mono_ptr()) -#define CACHED_RAW_MONO_CLASS(m_class) (GDMonoCache::cached_data.rawclass_##m_class) -#define CACHED_FIELD(m_class, m_field) (GDMonoCache::cached_data.field_##m_class##_##m_field) -#define CACHED_METHOD(m_class, m_method) (GDMonoCache::cached_data.method_##m_class##_##m_method) -#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) +#undef GD_CLR_STDCALL #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 deleted file mode 100644 index 51c5aa3542..0000000000 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ /dev/null @@ -1,576 +0,0 @@ -/*************************************************************************/ -/* gd_mono_class.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_class.h" - -#include <mono/metadata/attrdefs.h> -#include <mono/metadata/debug-helpers.h> - -#include "gd_mono_assembly.h" -#include "gd_mono_cache.h" -#include "gd_mono_marshal.h" - -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 = nullptr; - MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc); - UNHANDLED_EXCEPTION(exc); - - return GDMonoMarshal::mono_string_to_godot(str); -} - -MonoType *GDMonoClass::get_mono_type(MonoClass *p_mono_class) { - return mono_class_get_type(p_mono_class); -} - -String GDMonoClass::get_full_name() const { - return get_full_name(mono_class); -} - -String GDMonoClass::get_type_desc() const { - return GDMonoUtils::get_type_desc(get_mono_type()); -} - -MonoType *GDMonoClass::get_mono_type() const { - // Careful, you cannot compare two MonoType*. - // There is mono_metadata_type_equal, how is this different from comparing two MonoClass*? - return get_mono_type(mono_class); -} - -uint32_t GDMonoClass::get_flags() const { - return mono_class_get_flags(mono_class); -} - -bool GDMonoClass::is_static() const { - uint32_t static_class_flags = MONO_TYPE_ATTR_ABSTRACT | MONO_TYPE_ATTR_SEALED; - return (get_flags() & static_class_flags) == static_class_flags; -} - -bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { - return mono_class_is_assignable_from(mono_class, p_from->mono_class); -} - -StringName GDMonoClass::get_namespace() const { - GDMonoClass *nesting_class = get_nesting_class(); - if (!nesting_class) { - return namespace_name; - } - return nesting_class->get_namespace(); -} - -String GDMonoClass::get_name_for_lookup() const { - GDMonoClass *nesting_class = get_nesting_class(); - if (!nesting_class) { - return class_name; - } - return nesting_class->get_name_for_lookup() + "/" + class_name; -} - -GDMonoClass *GDMonoClass::get_parent_class() const { - MonoClass *parent_mono_class = mono_class_get_parent(mono_class); - return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : nullptr; -} - -GDMonoClass *GDMonoClass::get_nesting_class() const { - MonoClass *nesting_type = mono_class_get_nesting_type(mono_class); - return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : nullptr; -} - -#ifdef TOOLS_ENABLED -Vector<MonoClassField *> GDMonoClass::get_enum_fields() { - bool class_is_enum = mono_class_is_enum(mono_class); - ERR_FAIL_COND_V(!class_is_enum, Vector<MonoClassField *>()); - - Vector<MonoClassField *> enum_fields; - - 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. - // Enum constants are static, so we will use this to ignore the value__ field. - if (field_flags & MONO_FIELD_ATTR_PUBLIC && field_flags & MONO_FIELD_ATTR_STATIC) { - enum_fields.push_back(raw_field); - } - } - - return enum_fields; -} -#endif - -bool GDMonoClass::has_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, false); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoClass::get_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, nullptr); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoClass::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - - attributes = mono_custom_attrs_from_class(get_mono_ptr()); - attrs_fetched = true; -} - -void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base) { - CRASH_COND(!CACHED_CLASS(GodotObject)->is_assignable_from(this)); - - if (methods_fetched) { - return; - } - - void *iter = nullptr; - MonoMethod *raw_method = nullptr; - while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != nullptr) { - StringName name = String::utf8(mono_method_get_name(raw_method)); - - // get_method implicitly fetches methods and adds them to this->methods - GDMonoMethod *method = get_method(raw_method, name); - ERR_CONTINUE(!method); - - if (method->get_name() != name) { -#ifdef DEBUG_ENABLED - String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")"; - WARN_PRINT("Method '" + fullname + "' is hidden by Godot API method. Should be '" + - method->get_full_name_no_class() + "'. In class '" + namespace_name + "." + class_name + "'."); -#endif - continue; - } - -#ifdef DEBUG_ENABLED - // For debug builds, we also fetched from native base classes as well before if this is not a native base class. - // This allows us to warn the user here if they are using snake_case by mistake. - - if (p_native_base != this) { - GDMonoClass *native_top = p_native_base; - while (native_top) { - GDMonoMethod *m = native_top->get_method(name, method->get_parameters_count()); - - if (m && m->get_name() != name) { - // found - String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")"; - WARN_PRINT("Method '" + fullname + "' should be '" + m->get_full_name_no_class() + - "'. In class '" + namespace_name + "." + class_name + "'."); - break; - } - - if (native_top == CACHED_CLASS(GodotObject)) { - break; - } - - native_top = native_top->get_parent_class(); - } - } -#endif - - uint32_t flags = mono_method_get_flags(method->mono_method, nullptr); - - if (!(flags & MONO_METHOD_ATTR_VIRTUAL)) { - continue; - } - - // Virtual method of Godot Object derived type, let's try to find GodotMethod attribute - - GDMonoClass *top = p_native_base; - - while (top) { - GDMonoMethod *base_method = top->get_method(name, method->get_parameters_count()); - - if (base_method && base_method->has_attribute(CACHED_CLASS(GodotMethodAttribute))) { - // Found base method with GodotMethod attribute. - // We get the original API method name from this attribute. - // This name must point to the virtual method. - - MonoObject *attr = base_method->get_attribute(CACHED_CLASS(GodotMethodAttribute)); - - StringName godot_method_name = CACHED_FIELD(GodotMethodAttribute, methodName)->get_string_value(attr); -#ifdef DEBUG_ENABLED - CRASH_COND(godot_method_name == StringName()); -#endif - MethodKey key = MethodKey(godot_method_name, method->get_parameters_count()); - GDMonoMethod **existing_method = methods.getptr(key); - if (existing_method) { - memdelete(*existing_method); // Must delete old one - } - methods.insert(key, method); - - break; - } - - if (top == CACHED_CLASS(GodotObject)) { - break; - } - - top = top->get_parent_class(); - } - } - - methods_fetched = true; -} - -GDMonoMethod *GDMonoClass::get_fetched_method_unknown_params(const StringName &p_name) { - ERR_FAIL_COND_V(!methods_fetched, nullptr); - - for (const KeyValue<MethodKey, GDMonoMethod *> &E : methods) { - if (E.key.name == p_name) { - return E.value; - } - } - - return nullptr; -} - -bool GDMonoClass::has_fetched_method_unknown_params(const StringName &p_name) { - return get_fetched_method_unknown_params(p_name) != nullptr; -} - -bool GDMonoClass::implements_interface(GDMonoClass *p_interface) { - return mono_class_implements_interface(mono_class, p_interface->get_mono_ptr()); -} - -bool GDMonoClass::has_public_parameterless_ctor() { - GDMonoMethod *ctor = get_method(".ctor", 0); - return ctor && ctor->get_visibility() == IMonoClassMember::PUBLIC; -} - -GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, uint16_t p_params_count) { - MethodKey key = MethodKey(p_name, p_params_count); - - GDMonoMethod **match = methods.getptr(key); - - if (match) { - return *match; - } - - if (methods_fetched) { - return nullptr; - } - - MonoMethod *raw_method = mono_class_get_method_from_name(mono_class, String(p_name).utf8().get_data(), p_params_count); - - if (raw_method) { - GDMonoMethod *method = memnew(GDMonoMethod(p_name, raw_method)); - methods.insert(key, method); - - return method; - } - - return nullptr; -} - -GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method) { - MonoMethodSignature *sig = mono_method_signature(p_raw_method); - - int params_count = mono_signature_get_param_count(sig); - StringName method_name = String::utf8(mono_method_get_name(p_raw_method)); - - return get_method(p_raw_method, method_name, params_count); -} - -GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name) { - MonoMethodSignature *sig = mono_method_signature(p_raw_method); - int params_count = mono_signature_get_param_count(sig); - return get_method(p_raw_method, p_name, params_count); -} - -GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name, uint16_t p_params_count) { - ERR_FAIL_NULL_V(p_raw_method, nullptr); - - MethodKey key = MethodKey(p_name, p_params_count); - - GDMonoMethod **match = methods.getptr(key); - - if (match) { - return *match; - } - - GDMonoMethod *method = memnew(GDMonoMethod(p_name, p_raw_method)); - methods.insert(key, method); - - return method; -} - -GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, bool p_include_namespace) { - MonoMethodDesc *desc = mono_method_desc_new(p_description.utf8().get_data(), p_include_namespace); - MonoMethod *method = mono_method_desc_search_in_class(desc, mono_class); - mono_method_desc_free(desc); - - if (!method) { - return nullptr; - } - - ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, nullptr); - - return get_method(method); -} - -GDMonoField *GDMonoClass::get_field(const StringName &p_name) { - HashMap<StringName, GDMonoField *>::Iterator result = fields.find(p_name); - - if (result) { - return result->value; - } - - if (fields_fetched) { - return nullptr; - } - - MonoClassField *raw_field = mono_class_get_field_from_name(mono_class, String(p_name).utf8().get_data()); - - if (raw_field) { - GDMonoField *field = memnew(GDMonoField(raw_field, this)); - fields.insert(p_name, field); - - return field; - } - - return nullptr; -} - -const Vector<GDMonoField *> &GDMonoClass::get_all_fields() { - if (fields_fetched) { - return fields_list; - } - - void *iter = nullptr; - MonoClassField *raw_field = nullptr; - while ((raw_field = mono_class_get_fields(mono_class, &iter)) != nullptr) { - StringName name = String::utf8(mono_field_get_name(raw_field)); - - HashMap<StringName, GDMonoField *>::Iterator match = fields.find(name); - - if (match) { - fields_list.push_back(match->value); - } else { - GDMonoField *field = memnew(GDMonoField(raw_field, this)); - fields.insert(name, field); - fields_list.push_back(field); - } - } - - fields_fetched = true; - - return fields_list; -} - -GDMonoProperty *GDMonoClass::get_property(const StringName &p_name) { - HashMap<StringName, GDMonoProperty *>::Iterator result = properties.find(p_name); - - if (result) { - return result->value; - } - - if (properties_fetched) { - return nullptr; - } - - MonoProperty *raw_property = mono_class_get_property_from_name(mono_class, String(p_name).utf8().get_data()); - - if (raw_property) { - GDMonoProperty *property = memnew(GDMonoProperty(raw_property, this)); - properties.insert(p_name, property); - - return property; - } - - return nullptr; -} - -const Vector<GDMonoProperty *> &GDMonoClass::get_all_properties() { - if (properties_fetched) { - return properties_list; - } - - void *iter = nullptr; - MonoProperty *raw_property = nullptr; - while ((raw_property = mono_class_get_properties(mono_class, &iter)) != nullptr) { - StringName name = String::utf8(mono_property_get_name(raw_property)); - - HashMap<StringName, GDMonoProperty *>::Iterator match = properties.find(name); - - if (match) { - properties_list.push_back(match->value); - } else { - GDMonoProperty *property = memnew(GDMonoProperty(raw_property, this)); - properties.insert(name, property); - properties_list.push_back(property); - } - } - - properties_fetched = true; - - return properties_list; -} - -const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() { - if (delegates_fetched) { - return delegates_list; - } - - // If the class is generic we must use the generic type definition. - MonoClass *klass = mono_class; - if (mono_type_get_type(get_mono_type()) == MONO_TYPE_GENERICINST) { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), get_mono_type()); - GDMonoUtils::Marshal::get_generic_type_definition(reftype, &reftype); - MonoType *type = mono_reflection_type_get_type(reftype); - klass = mono_class_from_mono_type(type); - } - - void *iter = nullptr; - MonoClass *raw_class = nullptr; - while ((raw_class = mono_class_get_nested_types(klass, &iter)) != nullptr) { - if (mono_class_is_delegate(raw_class)) { - StringName name = String::utf8(mono_class_get_name(raw_class)); - - HashMap<StringName, GDMonoClass *>::Iterator match = delegates.find(name); - - if (match) { - delegates_list.push_back(match->value); - } else { - GDMonoClass *delegate = memnew(GDMonoClass(String::utf8(mono_class_get_namespace(raw_class)), String::utf8(mono_class_get_name(raw_class)), raw_class, assembly)); - delegates.insert(name, delegate); - delegates_list.push_back(delegate); - } - } - } - - delegates_fetched = true; - - return delegates_list; -} - -const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() { - if (!method_list_fetched) { - 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(String::utf8(mono_method_get_name(raw_method)), raw_method))); - } - - method_list_fetched = true; - } - - return method_list; -} - -GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly) { - namespace_name = p_namespace; - class_name = p_name; - mono_class = p_class; - assembly = p_assembly; - - attrs_fetched = false; - attributes = nullptr; - - methods_fetched = false; - method_list_fetched = false; - fields_fetched = false; - properties_fetched = false; - delegates_fetched = false; -} - -GDMonoClass::~GDMonoClass() { - if (attributes) { - mono_custom_attrs_free(attributes); - } - - for (const KeyValue<StringName, GDMonoField *> &E : fields) { - memdelete(E.value); - } - - for (const KeyValue<StringName, GDMonoProperty *> &E : properties) { - memdelete(E.value); - } - - { - // Ugly workaround... - // We may have duplicated values, because we redirect snake_case methods to PascalCasel (only Godot API methods). - // This way, we end with both the snake_case name and the PascalCasel name paired with the same method. - // Therefore, we must avoid deleting the same pointer twice. - - int offset = 0; - Vector<GDMonoMethod *> deleted_methods; - deleted_methods.resize(methods.size()); - - for (const KeyValue<MethodKey, GDMonoMethod *> &E : methods) { - GDMonoMethod *method = E.value; - - if (method) { - for (int i = 0; i < offset; i++) { - if (deleted_methods[i] == method) { - // Already deleted - goto already_deleted; - } - } - - deleted_methods.write[offset] = method; - ++offset; - - memdelete(method); - } - - already_deleted:; - } - - methods.clear(); - } - - for (int i = 0; i < method_list.size(); ++i) { - memdelete(method_list[i]); - } -} diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h deleted file mode 100644 index 6b35da30f9..0000000000 --- a/modules/mono/mono_gd/gd_mono_class.h +++ /dev/null @@ -1,160 +0,0 @@ -/*************************************************************************/ -/* gd_mono_class.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_CLASS_H -#define GD_MONO_CLASS_H - -#include "core/string/ustring.h" -#include "core/templates/rb_map.h" - -#include "gd_mono_field.h" -#include "gd_mono_header.h" -#include "gd_mono_method.h" -#include "gd_mono_property.h" -#include "gd_mono_utils.h" - -class GDMonoClass { - struct MethodKey { - struct Hasher { - static _FORCE_INLINE_ uint32_t hash(const MethodKey &p_key) { - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.name.hash()); - GDMonoUtils::hash_combine(hash, HashMapHasherDefault::hash(p_key.params_count)); - - return hash; - } - }; - - _FORCE_INLINE_ bool operator==(const MethodKey &p_a) const { - return p_a.params_count == params_count && p_a.name == name; - } - - MethodKey() {} - - MethodKey(const StringName &p_name, uint16_t p_params_count) : - name(p_name), params_count(p_params_count) { - } - - StringName name; - uint16_t params_count = 0; - }; - - StringName namespace_name; - StringName class_name; - - MonoClass *mono_class = nullptr; - GDMonoAssembly *assembly = nullptr; - - bool attrs_fetched; - MonoCustomAttrInfo *attributes = nullptr; - - // This contains both the original method names and remapped method names from the native Godot identifiers to the C# functions. - // Most method-related functions refer to this and it's possible this is unintuitive for outside users; this may be a prime location for refactoring or renaming. - bool methods_fetched; - HashMap<MethodKey, GDMonoMethod *, MethodKey::Hasher> methods; - - bool method_list_fetched; - Vector<GDMonoMethod *> method_list; - - bool fields_fetched; - HashMap<StringName, GDMonoField *> fields; - Vector<GDMonoField *> fields_list; - - bool properties_fetched; - HashMap<StringName, GDMonoProperty *> properties; - Vector<GDMonoProperty *> properties_list; - - bool delegates_fetched; - HashMap<StringName, GDMonoClass *> delegates; - Vector<GDMonoClass *> delegates_list; - - friend class GDMonoAssembly; - GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly); - -public: - static String get_full_name(MonoClass *p_mono_class); - static MonoType *get_mono_type(MonoClass *p_mono_class); - - String get_full_name() const; - String get_type_desc() const; - MonoType *get_mono_type() const; - - uint32_t get_flags() const; - bool is_static() const; - - bool is_assignable_from(GDMonoClass *p_from) const; - - StringName get_namespace() const; - _FORCE_INLINE_ StringName get_name() const { return class_name; } - String get_name_for_lookup() const; - - _FORCE_INLINE_ MonoClass *get_mono_ptr() const { return mono_class; } - _FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; } - - GDMonoClass *get_parent_class() const; - GDMonoClass *get_nesting_class() const; - -#ifdef TOOLS_ENABLED - Vector<MonoClassField *> get_enum_fields(); -#endif - - GDMonoMethod *get_fetched_method_unknown_params(const StringName &p_name); - bool has_fetched_method_unknown_params(const StringName &p_name); - - bool has_attribute(GDMonoClass *p_attr_class); - MonoObject *get_attribute(GDMonoClass *p_attr_class); - - void fetch_attributes(); - void fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base); - - bool implements_interface(GDMonoClass *p_interface); - bool has_public_parameterless_ctor(); - - GDMonoMethod *get_method(const StringName &p_name, uint16_t p_params_count = 0); - GDMonoMethod *get_method(MonoMethod *p_raw_method); - GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name); - GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, uint16_t p_params_count); - GDMonoMethod *get_method_with_desc(const String &p_description, bool p_include_namespace); - - GDMonoField *get_field(const StringName &p_name); - const Vector<GDMonoField *> &get_all_fields(); - - GDMonoProperty *get_property(const StringName &p_name); - const Vector<GDMonoProperty *> &get_all_properties(); - - const Vector<GDMonoClass *> &get_all_delegates(); - - const Vector<GDMonoMethod *> &get_all_methods(); - - ~GDMonoClass(); -}; - -#endif // GD_MONO_CLASS_H diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp deleted file mode 100644 index cb025fc67a..0000000000 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ /dev/null @@ -1,556 +0,0 @@ -/*************************************************************************/ -/* gd_mono_field.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_field.h" - -#include <mono/metadata/attrdefs.h> - -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#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); -} - -void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_value) { - switch (type.type_encoding) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_value.operator bool(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_CHAR: { - int16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I1: { - int8_t val = p_value.operator signed char(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I2: { - int16_t val = p_value.operator signed short(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I4: { - int32_t val = p_value.operator signed int(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I8: { - int64_t val = p_value.operator int64_t(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U1: { - uint8_t val = p_value.operator unsigned char(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U2: { - uint16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U4: { - uint32_t val = p_value.operator unsigned int(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U8: { - uint64_t val = p_value.operator uint64_t(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_R4: { - float val = p_value.operator float(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_R8: { - double val = p_value.operator double(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_VALUETYPE: { - GDMonoClass *tclass = type.type_class; - - if (tclass == CACHED_CLASS(Vector2)) { - GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_value.operator ::Vector2()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector2i)) { - GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_value.operator ::Vector2i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Rect2)) { - GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_value.operator ::Rect2()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Rect2i)) { - GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_value.operator ::Rect2i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Transform2D)) { - GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_value.operator ::Transform2D()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector3)) { - GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_value.operator ::Vector3()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector3i)) { - GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_value.operator ::Vector3i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector4)) { - GDMonoMarshal::M_Vector4 from = MARSHALLED_OUT(Vector4, p_value.operator ::Vector4()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector4i)) { - GDMonoMarshal::M_Vector4i from = MARSHALLED_OUT(Vector4i, p_value.operator ::Vector4i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Basis)) { - GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_value.operator ::Basis()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Quaternion)) { - GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_value.operator ::Quaternion()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Transform3D)) { - GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_value.operator ::Transform3D()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Projection)) { - GDMonoMarshal::M_Projection from = MARSHALLED_OUT(Projection, p_value.operator ::Projection()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(AABB)) { - GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_value.operator ::AABB()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Color)) { - GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_value.operator ::Color()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Plane)) { - GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_value.operator ::Plane()); - mono_field_set_value(p_object, mono_field, &from); - 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)) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_value.operator bool(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_CHAR: { - uint16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I1: { - int8_t val = p_value.operator signed char(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I2: { - int16_t val = p_value.operator signed short(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I4: { - int32_t val = p_value.operator signed int(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I8: { - int64_t val = p_value.operator int64_t(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U1: { - uint8_t val = p_value.operator unsigned char(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U2: { - uint16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U4: { - uint32_t val = p_value.operator unsigned int(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U8: { - uint64_t val = p_value.operator uint64_t(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - default: { - ERR_FAIL_MSG("Attempted to convert Variant to a managed enum value of unmarshallable base type."); - } - } - - break; - } - - ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + tclass->get_name() + "'."); - } break; - case MONO_TYPE_STRING: { - if (p_value.get_type() == Variant::NIL) { - // Otherwise, Variant -> String would return the 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); - mono_field_set_value(p_object, mono_field, mono_string); - } - } break; - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoArray *managed = GDMonoMarshal::variant_to_mono_array(p_value, type.type_class); - if (likely(managed != nullptr)) { - mono_field_set_value(p_object, mono_field, managed); - } - } break; - case MONO_TYPE_CLASS: { - MonoObject *managed = GDMonoMarshal::variant_to_mono_object_of_class(p_value, type.type_class); - if (likely(managed != nullptr)) { - mono_field_set_value(p_object, mono_field, managed); - } - } break; - case MONO_TYPE_GENERICINST: { - MonoObject *managed = GDMonoMarshal::variant_to_mono_object_of_genericinst(p_value, type.type_class); - if (likely(managed != nullptr)) { - mono_field_set_value(p_object, mono_field, managed); - } - } break; - case MONO_TYPE_OBJECT: { - // Variant - switch (p_value.get_type()) { - case Variant::BOOL: { - MonoBoolean val = p_value.operator bool(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case Variant::INT: { - int32_t val = p_value.operator signed int(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case Variant::FLOAT: { -#ifdef REAL_T_IS_DOUBLE - double val = p_value.operator double(); - mono_field_set_value(p_object, mono_field, &val); -#else - float val = p_value.operator float(); - mono_field_set_value(p_object, mono_field, &val); -#endif - } break; - case Variant::STRING: { - MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value); - mono_field_set_value(p_object, mono_field, mono_string); - } break; - case Variant::VECTOR2: { - GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_value.operator ::Vector2()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR2I: { - GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_value.operator ::Vector2i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::RECT2: { - GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_value.operator ::Rect2()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::RECT2I: { - GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_value.operator ::Rect2i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR3: { - GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_value.operator ::Vector3()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR3I: { - GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_value.operator ::Vector3i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR4: { - GDMonoMarshal::M_Vector4 from = MARSHALLED_OUT(Vector4, p_value.operator ::Vector4()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR4I: { - GDMonoMarshal::M_Vector4i from = MARSHALLED_OUT(Vector4i, p_value.operator ::Vector4i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::TRANSFORM2D: { - GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_value.operator ::Transform2D()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::PLANE: { - GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_value.operator ::Plane()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::QUATERNION: { - GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_value.operator ::Quaternion()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::AABB: { - GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_value.operator ::AABB()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::BASIS: { - GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_value.operator ::Basis()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::TRANSFORM3D: { - GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_value.operator ::Transform3D()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::PROJECTION: { - GDMonoMarshal::M_Projection from = MARSHALLED_OUT(Projection, p_value.operator ::Projection()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::COLOR: { - GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_value.operator ::Color()); - mono_field_set_value(p_object, mono_field, &from); - } 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); - } break; - case Variant::RID: { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator ::RID()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::OBJECT: { - MonoObject *managed = GDMonoUtils::unmanaged_get_managed(p_value.operator Object *()); - mono_field_set_value(p_object, mono_field, managed); - } 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); - } break; - case Variant::ARRAY: { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_BYTE_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedByteArray_to_mono_array(p_value.operator ::PackedByteArray()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_INT32_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedInt32Array_to_mono_array(p_value.operator ::PackedInt32Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_INT64_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedInt64Array_to_mono_array(p_value.operator ::PackedInt64Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_FLOAT32_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedFloat32Array_to_mono_array(p_value.operator ::PackedFloat32Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_FLOAT64_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedFloat64Array_to_mono_array(p_value.operator ::PackedFloat64Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_STRING_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedStringArray_to_mono_array(p_value.operator ::PackedStringArray()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_VECTOR2_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedVector2Array_to_mono_array(p_value.operator ::PackedVector2Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_VECTOR3_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedVector3Array_to_mono_array(p_value.operator ::PackedVector3Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_COLOR_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedColorArray_to_mono_array(p_value.operator ::PackedColorArray()); - mono_field_set_value(p_object, mono_field, managed); - } break; - default: - break; - } - } break; - default: { - ERR_PRINT("Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding) + "."); - } break; - } -} - -MonoObject *GDMonoField::get_value(MonoObject *p_object) { - return mono_field_get_value_object(mono_domain_get(), mono_field, p_object); -} - -bool GDMonoField::get_bool_value(MonoObject *p_object) { - return (bool)GDMonoMarshal::unbox<MonoBoolean>(get_value(p_object)); -} - -int GDMonoField::get_int_value(MonoObject *p_object) { - return GDMonoMarshal::unbox<int32_t>(get_value(p_object)); -} - -String GDMonoField::get_string_value(MonoObject *p_object) { - MonoObject *val = get_value(p_object); - return GDMonoMarshal::mono_string_to_godot((MonoString *)val); -} - -bool GDMonoField::has_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, false); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoField::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, nullptr); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoField::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - attributes = mono_custom_attrs_from_field(owner->get_mono_ptr(), mono_field); - attrs_fetched = true; -} - -bool GDMonoField::is_static() { - return mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_STATIC; -} - -IMonoClassMember::Visibility GDMonoField::get_visibility() { - switch (mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_FIELD_ACCESS_MASK) { - case MONO_FIELD_ATTR_PRIVATE: - return IMonoClassMember::PRIVATE; - case MONO_FIELD_ATTR_FAM_AND_ASSEM: - return IMonoClassMember::PROTECTED_AND_INTERNAL; - case MONO_FIELD_ATTR_ASSEMBLY: - return IMonoClassMember::INTERNAL; - case MONO_FIELD_ATTR_FAMILY: - return IMonoClassMember::PROTECTED; - case MONO_FIELD_ATTR_PUBLIC: - return IMonoClassMember::PUBLIC; - default: - ERR_FAIL_V(IMonoClassMember::PRIVATE); - } -} - -GDMonoField::GDMonoField(MonoClassField *p_mono_field, GDMonoClass *p_owner) { - owner = p_owner; - mono_field = p_mono_field; - name = String::utf8(mono_field_get_name(mono_field)); - MonoType *field_type = mono_field_get_type(mono_field); - type.type_encoding = mono_type_get_type(field_type); - MonoClass *field_type_class = mono_class_from_mono_type(field_type); - type.type_class = GDMono::get_singleton()->get_class(field_type_class); - - attrs_fetched = false; - attributes = nullptr; -} - -GDMonoField::~GDMonoField() { - if (attributes) { - mono_custom_attrs_free(attributes); - } -} diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h deleted file mode 100644 index 1d30f7a369..0000000000 --- a/modules/mono/mono_gd/gd_mono_field.h +++ /dev/null @@ -1,78 +0,0 @@ -/*************************************************************************/ -/* gd_mono_field.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_FIELD_H -#define GD_MONO_FIELD_H - -#include "gd_mono.h" -#include "gd_mono_header.h" -#include "i_mono_class_member.h" - -class GDMonoField : public IMonoClassMember { - GDMonoClass *owner = nullptr; - MonoClassField *mono_field = nullptr; - - StringName name; - ManagedType type; - - bool attrs_fetched; - MonoCustomAttrInfo *attributes = nullptr; - -public: - virtual GDMonoClass *get_enclosing_class() const final { return owner; } - - virtual MemberType get_member_type() const final { return MEMBER_TYPE_FIELD; } - - virtual StringName get_name() const final { return name; } - - virtual bool is_static() final; - virtual Visibility get_visibility() 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); - - MonoObject *get_value(MonoObject *p_object); - - bool get_bool_value(MonoObject *p_object); - int get_int_value(MonoObject *p_object); - String get_string_value(MonoObject *p_object); - - GDMonoField(MonoClassField *p_mono_field, GDMonoClass *p_owner); - ~GDMonoField(); -}; - -#endif // GD_MONO_FIELD_H diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp deleted file mode 100644 index d206b0dfc3..0000000000 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/*************************************************************************/ -/* gd_mono_internals.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_internals.h" - -#include "../csharp_script.h" -#include "../mono_gc_handle.h" -#include "../utils/macros.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 { -void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { - // This method should not fail - - CRASH_COND(!unmanaged); - - // All mono objects created from the managed world (e.g.: 'new Player()') - // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - - RefCounted *rc = Object::cast_to<RefCounted>(unmanaged); - - GDMonoClass *klass = GDMonoUtils::get_object_class(managed); - - CRASH_COND(!klass); - - GDMonoClass *native = GDMonoUtils::get_class_native_base(klass); - - CRASH_COND(native == nullptr); - - if (native == klass) { - // If it's just a wrapper Godot class and not a custom inheriting class, then attach a - // script binding instead. One of the advantages of this is that if a script is attached - // later and it's not a C# script, then the managed object won't have to be disposed. - // Another reason for doing this is that this instance could outlive CSharpLanguage, which would - // be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621 - - CSharpScriptBinding script_binding; - - script_binding.inited = true; - script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass); - script_binding.wrapper_class = klass; - script_binding.gchandle = rc ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed); - script_binding.owner = unmanaged; - - if (rc) { - // Unsafe refcount increment. The managed instance also counts as a reference. - // This way if the unmanaged world has no references to our owner - // but the managed instance is alive, the refcount will be 1 instead of 0. - // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) - - // May not me referenced yet, so we must use init_ref() instead of reference() - if (rc->init_ref()) { - CSharpLanguage::get_singleton()->post_unsafe_reference(rc); - } - } - - // The object was just created, no script instance binding should have been attached - CRASH_COND(CSharpLanguage::has_instance_binding(unmanaged)); - - void *data; - { - MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); - data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(unmanaged, script_binding); - } - - // Should be thread safe because the object was just created and nothing else should be referencing it - CSharpLanguage::set_instance_binding(unmanaged, data); - - return; - } - - MonoGCHandleData gchandle = rc ? 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()); - - CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle); - - unmanaged->set_script_and_instance(script, csharp_instance); -} - -void unhandled_exception(MonoException *p_exc) { - mono_print_unhandled_exception((MonoObject *)p_exc); - gd_unhandled_exception_event(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 - mono_unhandled_exception((MonoObject *)p_exc); - GDMono::unhandled_exception_hook((MonoObject *)p_exc, nullptr); - GD_UNREACHABLE(); - } else { -#ifdef DEBUG_ENABLED - GDMonoUtils::debug_send_unhandled_exception_error(p_exc); - if (EngineDebugger::is_active()) { - EngineDebugger::get_singleton()->poll_events(false); - } -#endif - } -} - -void gd_unhandled_exception_event(MonoException *p_exc) { - MonoImage *mono_image = GDMono::get_singleton()->get_core_api_assembly()->get_image(); - - MonoClass *gd_klass = mono_class_from_name(mono_image, "Godot", "GD"); - MonoMethod *unhandled_exception_method = mono_class_get_method_from_name(gd_klass, "OnUnhandledException", -1); - void *args[1]; - args[0] = p_exc; - mono_runtime_invoke(unhandled_exception_method, nullptr, (void **)args, nullptr); -} -} // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h deleted file mode 100644 index a8f9cfa3ca..0000000000 --- a/modules/mono/mono_gd/gd_mono_internals.h +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************/ -/* gd_mono_internals.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_INTERNALS_H -#define GD_MONO_INTERNALS_H - -#include <mono/jit/jit.h> - -#include "../utils/macros.h" - -#include "core/object/class_db.h" - -namespace GDMonoInternals { -void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); - -/** - * Do not call this function directly. - * Use GDMonoUtils::debug_unhandled_exception(MonoException *) instead. - */ -void unhandled_exception(MonoException *p_exc); - -void gd_unhandled_exception_event(MonoException *p_exc); -} // namespace GDMonoInternals - -#endif // GD_MONO_INTERNALS_H diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp deleted file mode 100644 index 6ea3c5539e..0000000000 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/*************************************************************************/ -/* gd_mono_log.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_log.h" - -#include <stdlib.h> // abort - -#include "core/io/dir_access.h" -#include "core/os/os.h" - -#include "../godotsharp_dirs.h" -#include "../utils/string_utils.h" - -static CharString get_default_log_level() { -#ifdef DEBUG_ENABLED - return String("info").utf8(); -#else - return String("warning").utf8(); -#endif -} - -GDMonoLog *GDMonoLog::singleton = nullptr; - -#ifdef GD_MONO_LOG_ENABLED - -static int get_log_level_id(const char *p_log_level) { - const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", nullptr }; - - int i = 0; - while (valid_log_levels[i]) { - if (!strcmp(valid_log_levels[i], p_log_level)) { - return i; - } - i++; - } - - return -1; -} - -static String make_text(const char *log_domain, const char *log_level, const char *message) { - String text(message); - text += " (in domain "; - text += log_domain; - if (log_level) { - text += ", "; - text += log_level; - } - text += ")"; - return text; -} - -void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *) { - if (GDMonoLog::get_singleton()->log_level_id >= get_log_level_id(log_level)) { - String text = make_text(log_domain, log_level, message); - text += "\n"; - - GDMonoLog::get_singleton()->log_file->seek_end(); - GDMonoLog::get_singleton()->log_file->store_string(text); - } - - if (fatal) { - String text = make_text(log_domain, log_level, message); - ERR_PRINT("Mono: FATAL ERROR '" + text + "', ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'."); - // Make sure to flush before aborting - GDMonoLog::get_singleton()->log_file->flush(); - GDMonoLog::get_singleton()->log_file.unref(); - - abort(); - } -} - -bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) { - if (!DirAccess::exists(p_logs_dir)) { - Ref<DirAccess> diraccess = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V(diraccess.is_null(), false); - Error logs_mkdir_err = diraccess->make_dir_recursive(p_logs_dir); - ERR_FAIL_COND_V_MSG(logs_mkdir_err != OK, false, "Failed to create mono logs directory."); - } - - return true; -} - -void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) { - static const uint64_t MAX_SECS = 5 * 86400; // 5 days - - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND(da.is_null()); - - Error err = da->change_dir(p_logs_dir); - ERR_FAIL_COND_MSG(err != OK, "Cannot change directory to '" + p_logs_dir + "'."); - - ERR_FAIL_COND(da->list_dir_begin() != OK); - - String current = da->get_next(); - while (!current.is_empty()) { - if (da->current_is_dir() || !current.ends_with(".txt")) { - current = da->get_next(); - continue; - } - - uint64_t modified_time = FileAccess::get_modified_time(da->get_current_dir().plus_file(current)); - - if (OS::get_singleton()->get_unix_time() - modified_time > MAX_SECS) { - da->remove(current); - } - current = da->get_next(); - } - - da->list_dir_end(); -} - -void GDMonoLog::initialize() { - CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8(); - - if (log_level.length() != 0 && get_log_level_id(log_level.get_data()) == -1) { - ERR_PRINT(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'."); - log_level = CharString(); - } - - if (log_level.length() == 0) { - log_level = get_default_log_level(); - } - - String logs_dir = GodotSharpDirs::get_mono_logs_dir(); - - if (_try_create_logs_dir(logs_dir)) { - _delete_old_log_files(logs_dir); - - OS::Date date_now = OS::get_singleton()->get_date(); - OS::Time time_now = OS::get_singleton()->get_time(); - - String log_file_name = str_format("%04d-%02d-%02d_%02d.%02d.%02d", - (int)date_now.year, (int)date_now.month, (int)date_now.day, - (int)time_now.hour, (int)time_now.minute, (int)time_now.second); - - log_file_name += str_format("_%d", OS::get_singleton()->get_process_id()); - - log_file_name += ".log"; - - log_file_path = logs_dir.plus_file(log_file_name); - - log_file = FileAccess::open(log_file_path, FileAccess::WRITE); - if (log_file.is_null()) { - ERR_PRINT("Mono: Cannot create log file at: " + log_file_path); - } - } - - mono_trace_set_level_string(log_level.get_data()); - log_level_id = get_log_level_id(log_level.get_data()); - - if (log_file.is_valid()) { - OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data()); - mono_trace_set_log_handler(mono_log_callback, this); - } else { - OS::get_singleton()->printerr("Mono: No log file, using default log handler\n"); - } -} - -GDMonoLog::GDMonoLog() { - singleton = this; -} - -GDMonoLog::~GDMonoLog() { - singleton = nullptr; -} - -#else - -void GDMonoLog::initialize() { - CharString log_level = get_default_log_level(); - mono_trace_set_level_string(log_level.get_data()); -} - -GDMonoLog::GDMonoLog() { - singleton = this; -} - -GDMonoLog::~GDMonoLog() { - 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 deleted file mode 100644 index 93ba6a410e..0000000000 --- a/modules/mono/mono_gd/gd_mono_log.h +++ /dev/null @@ -1,71 +0,0 @@ -/*************************************************************************/ -/* gd_mono_log.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_LOG_H -#define GD_MONO_LOG_H - -#include <mono/utils/mono-logger.h> - -#include "core/typedefs.h" - -#if !defined(JAVASCRIPT_ENABLED) && !defined(IOS_ENABLED) -// We have custom mono log callbacks for WASM and iOS -#define GD_MONO_LOG_ENABLED -#endif - -#ifdef GD_MONO_LOG_ENABLED -#include "core/io/file_access.h" -#endif - -class GDMonoLog { -#ifdef GD_MONO_LOG_ENABLED - int log_level_id = -1; - - Ref<FileAccess> log_file; - String log_file_path; - - bool _try_create_logs_dir(const String &p_logs_dir); - void _delete_old_log_files(const String &p_logs_dir); - - static void mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data); -#endif - - static GDMonoLog *singleton; - -public: - _FORCE_INLINE_ static GDMonoLog *get_singleton() { return singleton; } - - void initialize(); - - GDMonoLog(); - ~GDMonoLog(); -}; - -#endif // GD_MONO_LOG_H diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp deleted file mode 100644 index a860442764..0000000000 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ /dev/null @@ -1,1824 +0,0 @@ -/*************************************************************************/ -/* gd_mono_marshal.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "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, bool *r_nil_is_variant) { - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - return Variant::BOOL; - - case MONO_TYPE_I1: - return Variant::INT; - case MONO_TYPE_I2: - return Variant::INT; - case MONO_TYPE_I4: - return Variant::INT; - case MONO_TYPE_I8: - return Variant::INT; - - case MONO_TYPE_U1: - return Variant::INT; - case MONO_TYPE_U2: - return Variant::INT; - case MONO_TYPE_U4: - return Variant::INT; - case MONO_TYPE_U8: - return Variant::INT; - - case MONO_TYPE_R4: - return Variant::FLOAT; - case MONO_TYPE_R8: - return Variant::FLOAT; - - case MONO_TYPE_STRING: { - return Variant::STRING; - } break; - - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - - 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(Vector4)) { - return Variant::VECTOR4; - } - - if (vtclass == CACHED_CLASS(Vector4i)) { - return Variant::VECTOR4I; - } - - if (vtclass == CACHED_CLASS(Basis)) { - return Variant::BASIS; - } - - if (vtclass == CACHED_CLASS(Quaternion)) { - return Variant::QUATERNION; - } - - if (vtclass == CACHED_CLASS(Transform3D)) { - return Variant::TRANSFORM3D; - } - if (vtclass == CACHED_CLASS(Projection)) { - return Variant::PROJECTION; - } - if (vtclass == CACHED_CLASS(AABB)) { - return Variant::AABB; - } - - if (vtclass == CACHED_CLASS(Color)) { - return Variant::COLOR; - } - - 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; - - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoClass *elem_class = mono_class_get_element_class(p_type.type_class->get_mono_ptr()); - - if (elem_class == CACHED_CLASS_RAW(MonoObject)) { - return Variant::ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(uint8_t)) { - return Variant::PACKED_BYTE_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(int32_t)) { - return Variant::PACKED_INT32_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(int64_t)) { - return Variant::PACKED_INT64_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(float)) { - return Variant::PACKED_FLOAT32_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(double)) { - return Variant::PACKED_FLOAT64_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(String)) { - return Variant::PACKED_STRING_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(Vector2)) { - return Variant::PACKED_VECTOR2_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(Vector3)) { - return Variant::PACKED_VECTOR3_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(Color)) { - return Variant::PACKED_COLOR_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(StringName)) { - return Variant::ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(NodePath)) { - return Variant::ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(RID)) { - return Variant::ARRAY; - } - - if (mono_class_is_enum(elem_class)) { - return Variant::ARRAY; - } - - GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(elem_class); - if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) { - return Variant::ARRAY; - } - } break; - - case MONO_TYPE_CLASS: { - GDMonoClass *type_class = p_type.type_class; - - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - return Variant::OBJECT; - } - - if (CACHED_CLASS(StringName) == type_class) { - return Variant::STRING_NAME; - } - - if (CACHED_CLASS(NodePath) == type_class) { - return Variant::NODE_PATH; - } - - if (CACHED_CLASS(RID) == type_class) { - return Variant::RID; - } - - if (CACHED_CLASS(Dictionary) == type_class) { - return Variant::DICTIONARY; - } - - if (CACHED_CLASS(Array) == type_class) { - return Variant::ARRAY; - } - - // IDictionary - if (p_type.type_class == CACHED_CLASS(System_Collections_IDictionary)) { - return Variant::DICTIONARY; - } - - // ICollection or IEnumerable - if (p_type.type_class == CACHED_CLASS(System_Collections_ICollection) || - p_type.type_class == CACHED_CLASS(System_Collections_IEnumerable)) { - return Variant::ARRAY; - } - } 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()); - - // Godot.Collections.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { - return Variant::DICTIONARY; - } - - // Godot.Collections.Array<T> - if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { - return Variant::ARRAY; - } - - // System.Collections.Generic.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) { - return Variant::DICTIONARY; - } - - // System.Collections.Generic.List<T> - if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) { - return Variant::ARRAY; - } - - // IDictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_idictionary(reftype)) { - return Variant::DICTIONARY; - } - - // ICollection<T> or IEnumerable<T> - if (GDMonoUtils::Marshal::type_is_generic_icollection(reftype) || GDMonoUtils::Marshal::type_is_generic_ienumerable(reftype)) { - return Variant::ARRAY; - } - - // GodotObject - GDMonoClass *type_class = p_type.type_class; - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - return Variant::OBJECT; - } - } break; - - default: { - } break; - } - - if (r_nil_is_variant) { - *r_nil_is_variant = false; - } - - // Unknown - return Variant::NIL; -} - -bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) { - switch (p_array_type.type_encoding) { - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoClass *elem_class = mono_class_get_element_class(p_array_type.type_class->get_mono_ptr()); - r_elem_type = ManagedType::from_class(elem_class); - return true; - } break; - case MONO_TYPE_GENERICINST: { - MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type()); - - if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype) || - GDMonoUtils::Marshal::type_is_system_generic_list(array_reftype) || - GDMonoUtils::Marshal::type_is_generic_icollection(array_reftype) || - GDMonoUtils::Marshal::type_is_generic_ienumerable(array_reftype)) { - MonoReflectionType *elem_reftype; - - GDMonoUtils::Marshal::array_get_element_type(array_reftype, &elem_reftype); - - r_elem_type = ManagedType::from_reftype(elem_reftype); - return true; - } - } break; - default: { - } break; - } - - return false; -} - -MonoString *variant_to_mono_string(const Variant &p_var) { - if (p_var.get_type() == Variant::NIL) { - return nullptr; // Otherwise, Variant -> String would return the string "Null" - } - return mono_string_from_godot(p_var.operator String()); -} - -MonoArray *variant_to_mono_array(const Variant &p_var, GDMonoClass *p_type_class) { - MonoArrayType *array_type = mono_type_get_array_type(p_type_class->get_mono_type()); - - if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) { - return PackedByteArray_to_mono_array(p_var.operator PackedByteArray()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) { - return PackedInt32Array_to_mono_array(p_var.operator PackedInt32Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) { - return PackedInt64Array_to_mono_array(p_var.operator PackedInt64Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(float)) { - return PackedFloat32Array_to_mono_array(p_var.operator PackedFloat32Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(double)) { - return PackedFloat64Array_to_mono_array(p_var.operator PackedFloat64Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(String)) { - return PackedStringArray_to_mono_array(p_var.operator PackedStringArray()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) { - return PackedVector2Array_to_mono_array(p_var.operator PackedVector2Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) { - return PackedVector3Array_to_mono_array(p_var.operator PackedVector3Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Color)) { - return PackedColorArray_to_mono_array(p_var.operator PackedColorArray()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(StringName)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(NodePath)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(RID)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (mono_class_is_assignable_from(CACHED_CLASS(GodotObject)->get_mono_ptr(), array_type->eklass)) { - return Array_to_mono_array(p_var.operator ::Array(), array_type->eklass); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to array of unsupported element type:" + GDMonoClass::get_full_name(array_type->eklass) + "'."); -} - -MonoObject *variant_to_mono_object_of_class(const Variant &p_var, GDMonoClass *p_type_class) { - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(p_type_class)) { - return GDMonoUtils::unmanaged_get_managed(p_var.operator Object *()); - } - - if (CACHED_CLASS(StringName) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator StringName()); - } - - if (CACHED_CLASS(NodePath) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator NodePath()); - } - - if (CACHED_CLASS(RID) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator ::RID()); - } - - // Godot.Collections.Dictionary or IDictionary - if (CACHED_CLASS(Dictionary) == p_type_class || CACHED_CLASS(System_Collections_IDictionary) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), CACHED_CLASS(Dictionary)); - } - - // Godot.Collections.Array or ICollection or IEnumerable - if (CACHED_CLASS(Array) == p_type_class || - CACHED_CLASS(System_Collections_ICollection) == p_type_class || - CACHED_CLASS(System_Collections_IEnumerable) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator Array(), CACHED_CLASS(Array)); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type: '" + p_type_class->get_full_name() + "'."); -} - -MonoObject *variant_to_mono_object_of_genericinst(const Variant &p_var, GDMonoClass *p_type_class) { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type_class->get_mono_type()); - - // Godot.Collections.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { - return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), p_type_class); - } - - // Godot.Collections.Array<T> - if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { - return GDMonoUtils::create_managed_from(p_var.operator Array(), p_type_class); - } - - // System.Collections.Generic.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) { - MonoReflectionType *key_reftype = nullptr; - MonoReflectionType *value_reftype = nullptr; - GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype); - return Dictionary_to_system_generic_dict(p_var.operator Dictionary(), p_type_class, key_reftype, value_reftype); - } - - // System.Collections.Generic.List<T> - if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) { - MonoReflectionType *elem_reftype = nullptr; - GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype); - return Array_to_system_generic_list(p_var.operator Array(), p_type_class, elem_reftype); - } - - // IDictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_idictionary(reftype)) { - MonoReflectionType *key_reftype; - MonoReflectionType *value_reftype; - GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype); - GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype); - - return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), godot_dict_class); - } - - // ICollection<T> or IEnumerable<T> - if (GDMonoUtils::Marshal::type_is_generic_icollection(reftype) || GDMonoUtils::Marshal::type_is_generic_ienumerable(reftype)) { - MonoReflectionType *elem_reftype; - GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype); - GDMonoClass *godot_array_class = GDMonoUtils::Marshal::make_generic_array_type(elem_reftype); - - return GDMonoUtils::create_managed_from(p_var.operator Array(), godot_array_class); - } - - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(p_type_class)) { - return GDMonoUtils::unmanaged_get_managed(p_var.operator Object *()); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported generic type: '" + p_type_class->get_full_name() + "'."); -} - -MonoObject *variant_to_mono_object(const Variant &p_var) { - // Variant - switch (p_var.get_type()) { - case Variant::BOOL: { - MonoBoolean val = p_var.operator bool(); - return BOX_BOOLEAN(val); - } - case Variant::INT: { - int64_t val = p_var.operator int64_t(); - return BOX_INT64(val); - } - case Variant::FLOAT: { -#ifdef REAL_T_IS_DOUBLE - double val = p_var.operator double(); - return BOX_DOUBLE(val); -#else - float val = p_var.operator float(); - return BOX_FLOAT(val); -#endif - } - case Variant::STRING: - return (MonoObject *)mono_string_from_godot(p_var.operator String()); - case Variant::VECTOR2: { - GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var.operator ::Vector2()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from); - } - case Variant::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); - } - case Variant::VECTOR4: { - GDMonoMarshal::M_Vector4 from = MARSHALLED_OUT(Vector4, p_var.operator ::Vector4()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector4), &from); - } - case Variant::VECTOR4I: { - GDMonoMarshal::M_Vector4i from = MARSHALLED_OUT(Vector4i, p_var.operator ::Vector4i()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector4i), &from); - } - case Variant::PLANE: { - GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var.operator ::Plane()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from); - } - case Variant::QUATERNION: { - GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_var.operator ::Quaternion()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quaternion), &from); - } - case Variant::AABB: { - GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var.operator ::AABB()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from); - } - case Variant::BASIS: { - GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var.operator ::Basis()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from); - } - case Variant::TRANSFORM3D: { - GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_var.operator ::Transform3D()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform3D), &from); - } - case Variant::PROJECTION: { - GDMonoMarshal::M_Projection from = MARSHALLED_OUT(Projection, p_var.operator ::Projection()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Projection), &from); - } - case Variant::COLOR: { - GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var.operator ::Color()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from); - } - case Variant::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: - return GDMonoUtils::create_managed_from(p_var.operator Array(), CACHED_CLASS(Array)); - case Variant::PACKED_BYTE_ARRAY: - 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: - return (MonoObject *)PackedVector2Array_to_mono_array(p_var.operator PackedVector2Array()); - case Variant::PACKED_VECTOR3_ARRAY: - return (MonoObject *)PackedVector3Array_to_mono_array(p_var.operator PackedVector3Array()); - case Variant::PACKED_COLOR_ARRAY: - return (MonoObject *)PackedColorArray_to_mono_array(p_var.operator PackedColorArray()); - default: - return nullptr; - } -} - -size_t variant_get_managed_unboxed_size(const ManagedType &p_type) { - // This method prints no errors for unsupported types. It's called on all methods, not only - // those that end up being invoked with Variant parameters. - - // For MonoObject* we return 0, as it doesn't need to be stored. - constexpr size_t zero_for_mono_object = 0; - - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - return sizeof(MonoBoolean); - case MONO_TYPE_CHAR: - return sizeof(uint16_t); - case MONO_TYPE_I1: - return sizeof(int8_t); - case MONO_TYPE_I2: - return sizeof(int16_t); - case MONO_TYPE_I4: - return sizeof(int32_t); - case MONO_TYPE_I8: - return sizeof(int64_t); - case MONO_TYPE_U1: - return sizeof(uint8_t); - case MONO_TYPE_U2: - return sizeof(uint16_t); - case MONO_TYPE_U4: - return sizeof(uint32_t); - case MONO_TYPE_U8: - return sizeof(uint64_t); - case MONO_TYPE_R4: - return sizeof(float); - case MONO_TYPE_R8: - return sizeof(double); - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - -#define RETURN_CHECK_FOR_STRUCT(m_struct) \ - if (vtclass == CACHED_CLASS(m_struct)) { \ - return sizeof(M_##m_struct); \ - } - - RETURN_CHECK_FOR_STRUCT(Vector2); - RETURN_CHECK_FOR_STRUCT(Vector2i); - RETURN_CHECK_FOR_STRUCT(Rect2); - RETURN_CHECK_FOR_STRUCT(Rect2i); - RETURN_CHECK_FOR_STRUCT(Transform2D); - RETURN_CHECK_FOR_STRUCT(Vector3); - RETURN_CHECK_FOR_STRUCT(Vector3i); - RETURN_CHECK_FOR_STRUCT(Basis); - RETURN_CHECK_FOR_STRUCT(Quaternion); - RETURN_CHECK_FOR_STRUCT(Transform3D); - RETURN_CHECK_FOR_STRUCT(AABB); - RETURN_CHECK_FOR_STRUCT(Color); - RETURN_CHECK_FOR_STRUCT(Plane); - RETURN_CHECK_FOR_STRUCT(Callable); - RETURN_CHECK_FOR_STRUCT(SignalInfo); - -#undef RETURN_CHECK_FOR_STRUCT - - if (mono_class_is_enum(vtclass->get_mono_ptr())) { - MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr()); - switch (mono_type_get_type(enum_basetype)) { - case MONO_TYPE_BOOLEAN: - return sizeof(MonoBoolean); - case MONO_TYPE_CHAR: - return sizeof(uint16_t); - case MONO_TYPE_I1: - return sizeof(int8_t); - case MONO_TYPE_I2: - return sizeof(int16_t); - case MONO_TYPE_I4: - return sizeof(int32_t); - case MONO_TYPE_I8: - return sizeof(int64_t); - case MONO_TYPE_U1: - return sizeof(uint8_t); - case MONO_TYPE_U2: - return sizeof(uint16_t); - case MONO_TYPE_U4: - return sizeof(uint32_t); - case MONO_TYPE_U8: - return sizeof(uint64_t); - default: { - // Enum with unsupported base type. We return nullptr MonoObject* on error. - return zero_for_mono_object; - } - } - } - - // Enum with unsupported value type. We return nullptr MonoObject* on error. - } break; - case MONO_TYPE_STRING: - return zero_for_mono_object; - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_CLASS: - case MONO_TYPE_GENERICINST: - return zero_for_mono_object; - case MONO_TYPE_OBJECT: - return zero_for_mono_object; - } - - // Unsupported type encoding. We return nullptr MonoObject* on error. - return zero_for_mono_object; -} - -void *variant_to_managed_unboxed(const Variant &p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset) { -#define RETURN_TYPE_VAL(m_type, m_val) \ - *reinterpret_cast<m_type *>(r_buffer) = m_val; \ - r_offset += sizeof(m_type); \ - return r_buffer; - - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - RETURN_TYPE_VAL(MonoBoolean, (MonoBoolean)p_var.operator bool()); - case MONO_TYPE_CHAR: - RETURN_TYPE_VAL(uint16_t, p_var.operator unsigned short()); - case MONO_TYPE_I1: - RETURN_TYPE_VAL(int8_t, p_var.operator signed char()); - case MONO_TYPE_I2: - RETURN_TYPE_VAL(int16_t, p_var.operator signed short()); - case MONO_TYPE_I4: - RETURN_TYPE_VAL(int32_t, p_var.operator signed int()); - case MONO_TYPE_I8: - RETURN_TYPE_VAL(int64_t, p_var.operator int64_t()); - case MONO_TYPE_U1: - RETURN_TYPE_VAL(uint8_t, p_var.operator unsigned char()); - case MONO_TYPE_U2: - RETURN_TYPE_VAL(uint16_t, p_var.operator unsigned short()); - case MONO_TYPE_U4: - RETURN_TYPE_VAL(uint32_t, p_var.operator unsigned int()); - case MONO_TYPE_U8: - RETURN_TYPE_VAL(uint64_t, p_var.operator uint64_t()); - case MONO_TYPE_R4: - RETURN_TYPE_VAL(float, p_var.operator float()); - case MONO_TYPE_R8: - RETURN_TYPE_VAL(double, p_var.operator double()); - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - -#define RETURN_CHECK_FOR_STRUCT(m_struct) \ - if (vtclass == CACHED_CLASS(m_struct)) { \ - GDMonoMarshal::M_##m_struct from = MARSHALLED_OUT(m_struct, p_var.operator ::m_struct()); \ - RETURN_TYPE_VAL(M_##m_struct, from); \ - } - - RETURN_CHECK_FOR_STRUCT(Vector2); - RETURN_CHECK_FOR_STRUCT(Vector2i); - RETURN_CHECK_FOR_STRUCT(Rect2); - RETURN_CHECK_FOR_STRUCT(Rect2i); - RETURN_CHECK_FOR_STRUCT(Transform2D); - RETURN_CHECK_FOR_STRUCT(Vector3); - RETURN_CHECK_FOR_STRUCT(Vector3i); - RETURN_CHECK_FOR_STRUCT(Basis); - RETURN_CHECK_FOR_STRUCT(Quaternion); - RETURN_CHECK_FOR_STRUCT(Transform3D); - RETURN_CHECK_FOR_STRUCT(AABB); - RETURN_CHECK_FOR_STRUCT(Color); - RETURN_CHECK_FOR_STRUCT(Plane); - -#undef RETURN_CHECK_FOR_STRUCT - - if (vtclass == CACHED_CLASS(Callable)) { - GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var.operator Callable()); - RETURN_TYPE_VAL(M_Callable, from); - } - - if (vtclass == CACHED_CLASS(SignalInfo)) { - GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var.operator Signal()); - RETURN_TYPE_VAL(M_SignalInfo, from); - } - - if (mono_class_is_enum(vtclass->get_mono_ptr())) { - MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr()); - switch (mono_type_get_type(enum_basetype)) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_var.operator bool(); - RETURN_TYPE_VAL(MonoBoolean, val); - } - case MONO_TYPE_CHAR: { - uint16_t val = p_var.operator unsigned short(); - RETURN_TYPE_VAL(uint16_t, val); - } - case MONO_TYPE_I1: { - int8_t val = p_var.operator signed char(); - RETURN_TYPE_VAL(int8_t, val); - } - case MONO_TYPE_I2: { - int16_t val = p_var.operator signed short(); - RETURN_TYPE_VAL(int16_t, val); - } - case MONO_TYPE_I4: { - int32_t val = p_var.operator signed int(); - RETURN_TYPE_VAL(int32_t, val); - } - case MONO_TYPE_I8: { - int64_t val = p_var.operator int64_t(); - RETURN_TYPE_VAL(int64_t, val); - } - case MONO_TYPE_U1: { - uint8_t val = p_var.operator unsigned char(); - RETURN_TYPE_VAL(uint8_t, val); - } - case MONO_TYPE_U2: { - uint16_t val = p_var.operator unsigned short(); - RETURN_TYPE_VAL(uint16_t, val); - } - case MONO_TYPE_U4: { - uint32_t val = p_var.operator unsigned int(); - RETURN_TYPE_VAL(uint32_t, val); - } - case MONO_TYPE_U8: { - uint64_t val = p_var.operator uint64_t(); - RETURN_TYPE_VAL(uint64_t, val); - } - default: { - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to enum value of unsupported base type: '" + GDMonoClass::get_full_name(mono_class_from_mono_type(enum_basetype)) + "'."); - } - } - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported value type: '" + p_type.type_class->get_full_name() + "'."); - } break; -#undef RETURN_TYPE_VAL - case MONO_TYPE_STRING: - return variant_to_mono_string(p_var); - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - return variant_to_mono_array(p_var, p_type.type_class); - case MONO_TYPE_CLASS: - return variant_to_mono_object_of_class(p_var, p_type.type_class); - case MONO_TYPE_GENERICINST: - return variant_to_mono_object_of_genericinst(p_var, p_type.type_class); - case MONO_TYPE_OBJECT: - return variant_to_mono_object(p_var); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type with encoding: " + itos(p_type.type_encoding) + "."); -} - -MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type) { - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_var.operator bool(); - return BOX_BOOLEAN(val); - } - case MONO_TYPE_CHAR: { - uint16_t val = p_var.operator unsigned short(); - return BOX_UINT16(val); - } - case MONO_TYPE_I1: { - int8_t val = p_var.operator signed char(); - return BOX_INT8(val); - } - case MONO_TYPE_I2: { - int16_t val = p_var.operator signed short(); - return BOX_INT16(val); - } - case MONO_TYPE_I4: { - int32_t val = p_var.operator signed int(); - return BOX_INT32(val); - } - case MONO_TYPE_I8: { - int64_t val = p_var.operator int64_t(); - return BOX_INT64(val); - } - case MONO_TYPE_U1: { - uint8_t val = p_var.operator unsigned char(); - return BOX_UINT8(val); - } - case MONO_TYPE_U2: { - uint16_t val = p_var.operator unsigned short(); - return BOX_UINT16(val); - } - case MONO_TYPE_U4: { - uint32_t val = p_var.operator unsigned int(); - return BOX_UINT32(val); - } - case MONO_TYPE_U8: { - uint64_t val = p_var.operator uint64_t(); - return BOX_UINT64(val); - } - case MONO_TYPE_R4: { - float val = p_var.operator float(); - return BOX_FLOAT(val); - } - case MONO_TYPE_R8: { - double val = p_var.operator double(); - return BOX_DOUBLE(val); - } - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - -#define RETURN_CHECK_FOR_STRUCT(m_struct) \ - if (vtclass == CACHED_CLASS(m_struct)) { \ - GDMonoMarshal::M_##m_struct from = MARSHALLED_OUT(m_struct, p_var.operator ::m_struct()); \ - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(m_struct), &from); \ - } - - RETURN_CHECK_FOR_STRUCT(Vector2); - RETURN_CHECK_FOR_STRUCT(Vector2i); - RETURN_CHECK_FOR_STRUCT(Rect2); - RETURN_CHECK_FOR_STRUCT(Rect2i); - RETURN_CHECK_FOR_STRUCT(Transform2D); - RETURN_CHECK_FOR_STRUCT(Vector3); - RETURN_CHECK_FOR_STRUCT(Vector3i); - RETURN_CHECK_FOR_STRUCT(Basis); - RETURN_CHECK_FOR_STRUCT(Quaternion); - RETURN_CHECK_FOR_STRUCT(Transform3D); - RETURN_CHECK_FOR_STRUCT(AABB); - RETURN_CHECK_FOR_STRUCT(Color); - RETURN_CHECK_FOR_STRUCT(Plane); - -#undef RETURN_CHECK_FOR_STRUCT - - 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); - switch (mono_type_get_type(enum_basetype)) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_var.operator bool(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_CHAR: { - uint16_t val = p_var.operator unsigned short(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I1: { - int8_t val = p_var.operator signed char(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I2: { - int16_t val = p_var.operator signed short(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I4: { - int32_t val = p_var.operator signed int(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I8: { - int64_t val = p_var.operator int64_t(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U1: { - uint8_t val = p_var.operator unsigned char(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U2: { - uint16_t val = p_var.operator unsigned short(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U4: { - uint32_t val = p_var.operator unsigned int(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U8: { - uint64_t val = p_var.operator uint64_t(); - return BOX_ENUM(enum_baseclass, val); - } - default: { - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to enum value of unsupported base type: '" + GDMonoClass::get_full_name(enum_baseclass) + "'."); - } - } - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported value type: '" + p_type.type_class->get_full_name() + "'."); - } break; - case MONO_TYPE_STRING: - return (MonoObject *)variant_to_mono_string(p_var); - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - return (MonoObject *)variant_to_mono_array(p_var, p_type.type_class); - case MONO_TYPE_CLASS: - return variant_to_mono_object_of_class(p_var, p_type.type_class); - case MONO_TYPE_GENERICINST: - return variant_to_mono_object_of_genericinst(p_var, p_type.type_class); - case MONO_TYPE_OBJECT: - return variant_to_mono_object(p_var); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type with 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) { - ERR_FAIL_COND_V(!p_type.type_class, Variant()); - -#ifdef DEBUG_ENABLED - CRASH_COND_MSG(p_type.type_encoding == MONO_TYPE_OBJECT, "Type of object should be known."); -#endif - - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - return (bool)unbox<MonoBoolean>(p_obj); - case MONO_TYPE_CHAR: - return unbox<uint16_t>(p_obj); - case MONO_TYPE_I1: - return unbox<int8_t>(p_obj); - case MONO_TYPE_I2: - return unbox<int16_t>(p_obj); - case MONO_TYPE_I4: - return unbox<int32_t>(p_obj); - case MONO_TYPE_I8: - return unbox<int64_t>(p_obj); - case MONO_TYPE_U1: - return unbox<uint8_t>(p_obj); - case MONO_TYPE_U2: - return unbox<uint16_t>(p_obj); - case MONO_TYPE_U4: - return unbox<uint32_t>(p_obj); - case MONO_TYPE_U8: - return unbox<uint64_t>(p_obj); - case MONO_TYPE_R4: - return unbox<float>(p_obj); - case MONO_TYPE_R8: - return unbox<double>(p_obj); - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - - if (vtclass == CACHED_CLASS(Vector2)) { - 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, 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, unbox_addr<GDMonoMarshal::M_Transform2D>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Vector3)) { - 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, unbox_addr<GDMonoMarshal::M_Basis>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Quaternion)) { - return MARSHALLED_IN(Quaternion, unbox_addr<GDMonoMarshal::M_Quaternion>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Transform3D)) { - return MARSHALLED_IN(Transform3D, unbox_addr<GDMonoMarshal::M_Transform3D>(p_obj)); - } - - if (vtclass == CACHED_CLASS(AABB)) { - return MARSHALLED_IN(AABB, unbox_addr<GDMonoMarshal::M_AABB>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Color)) { - return MARSHALLED_IN(Color, unbox_addr<GDMonoMarshal::M_Color>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Plane)) { - 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); - } - } break; - case MONO_TYPE_STRING: { - if (p_obj == nullptr) { - return Variant(); // NIL - } - return mono_string_to_godot_not_null((MonoString *)p_obj); - } break; - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type()); - - if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) { - return mono_array_to_PackedByteArray((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) { - return mono_array_to_PackedInt32Array((MonoArray *)p_obj); - } - - 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); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) { - return mono_array_to_PackedVector2Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) { - return mono_array_to_PackedVector3Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Color)) { - return mono_array_to_PackedColorArray((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(StringName)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(NodePath)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(RID)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(array_type->eklass); - if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (p_fail_with_err) { - ERR_FAIL_V_MSG(Variant(), "Attempted to convert a managed array of unmarshallable element type to Variant."); - } else { - return Variant(); - } - } break; - case MONO_TYPE_CLASS: { - GDMonoClass *type_class = p_type.type_class; - - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - Object *ptr = unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_obj)); - if (ptr != nullptr) { - RefCounted *rc = Object::cast_to<RefCounted>(ptr); - return rc ? Variant(Ref<RefCounted>(rc)) : 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(); - } - - if (CACHED_CLASS(RID) == type_class) { - RID *ptr = unbox<RID *>(CACHED_FIELD(RID, ptr)->get_value(p_obj)); - return ptr ? Variant(*ptr) : Variant(); - } - - // Godot.Collections.Dictionary - if (CACHED_CLASS(Dictionary) == type_class) { - MonoException *exc = nullptr; - Dictionary *ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr).invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return ptr ? Variant(*ptr) : Variant(); - } - - // Godot.Collections.Array - if (CACHED_CLASS(Array) == type_class) { - MonoException *exc = nullptr; - Array *ptr = CACHED_METHOD_THUNK(Array, GetPtr).invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return ptr ? Variant(*ptr) : Variant(); - } - } break; - case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); - - // Godot.Collections.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { - MonoException *exc = nullptr; - MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return *unbox<Dictionary *>(ret); - } - - // Godot.Collections.Array<T> - if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { - MonoException *exc = nullptr; - MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return *unbox<Array *>(ret); - } - - // System.Collections.Generic.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) { - MonoReflectionType *key_reftype = nullptr; - MonoReflectionType *value_reftype = nullptr; - GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype); - return system_generic_dict_to_Dictionary(p_obj, p_type.type_class, key_reftype, value_reftype); - } - - // System.Collections.Generic.List<T> - if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) { - MonoReflectionType *elem_reftype = nullptr; - GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype); - return system_generic_list_to_Array_variant(p_obj, p_type.type_class, elem_reftype); - } - - // GodotObject - GDMonoClass *type_class = p_type.type_class; - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - Object *ptr = unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_obj)); - if (ptr != nullptr) { - RefCounted *rc = Object::cast_to<RefCounted>(ptr); - return rc ? Variant(Ref<RefCounted>(rc)) : Variant(ptr); - } - return Variant(); - } - } break; - } - - if (p_fail_with_err) { - ERR_FAIL_V_MSG(Variant(), "Attempted to convert an unmarshallable managed type to Variant. Name: '" + p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + "."); - } else { - return Variant(); - } -} - -Variant mono_object_to_variant(MonoObject *p_obj) { - if (!p_obj) { - return Variant(); - } - - ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj)); - - return mono_object_to_variant_impl(p_obj, type); -} - -Variant mono_object_to_variant(MonoObject *p_obj, const ManagedType &p_type) { - if (!p_obj) { - return Variant(); - } - - return mono_object_to_variant_impl(p_obj, p_type); -} - -Variant mono_object_to_variant_no_err(MonoObject *p_obj, const ManagedType &p_type) { - if (!p_obj) { - return Variant(); - } - - return mono_object_to_variant_impl(p_obj, p_type, /* fail_with_err: */ false); -} - -String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc) { - if (p_obj == nullptr) { - return String("null"); - } - - ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj)); - Variant var = GDMonoMarshal::mono_object_to_variant_no_err(p_obj, type); - - if (var.get_type() == Variant::NIL) { // `&& p_obj != nullptr` but omitted because always true - // Cannot convert MonoObject* to Variant; fallback to 'ToString()'. - MonoException *exc = nullptr; - MonoString *mono_str = GDMonoUtils::object_to_string(p_obj, &exc); - - if (exc) { - if (r_exc) { - *r_exc = exc; - } - return String(); - } - - return GDMonoMarshal::mono_string_to_godot(mono_str); - } else { - return var.operator String(); - } -} - -MonoObject *Dictionary_to_system_generic_dict(const Dictionary &p_dict, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { - String ctor_desc = ":.ctor(System.Collections.Generic.IDictionary`2<" + GDMonoUtils::get_type_desc(p_key_reftype) + - ", " + GDMonoUtils::get_type_desc(p_value_reftype) + ">)"; - GDMonoMethod *ctor = p_class->get_method_with_desc(ctor_desc, true); - CRASH_COND(ctor == nullptr); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, nullptr); - - GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(p_key_reftype, p_value_reftype); - MonoObject *godot_dict = GDMonoUtils::create_managed_from(p_dict, godot_dict_class); - - void *ctor_args[1] = { godot_dict }; - - MonoException *exc = nullptr; - ctor->invoke_raw(mono_object, ctor_args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -Dictionary system_generic_dict_to_Dictionary(MonoObject *p_obj, [[maybe_unused]] GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { - GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(p_key_reftype, p_value_reftype); - String ctor_desc = ":.ctor(System.Collections.Generic.IDictionary`2<" + GDMonoUtils::get_type_desc(p_key_reftype) + - ", " + GDMonoUtils::get_type_desc(p_value_reftype) + ">)"; - GDMonoMethod *godot_dict_ctor = godot_dict_class->get_method_with_desc(ctor_desc, true); - CRASH_COND(godot_dict_ctor == nullptr); - - MonoObject *godot_dict = mono_object_new(mono_domain_get(), godot_dict_class->get_mono_ptr()); - ERR_FAIL_NULL_V(godot_dict, Dictionary()); - - void *ctor_args[1] = { p_obj }; - - MonoException *exc = nullptr; - godot_dict_ctor->invoke_raw(godot_dict, ctor_args, &exc); - UNHANDLED_EXCEPTION(exc); - - exc = nullptr; - MonoObject *ret = godot_dict_class->get_method("GetPtr")->invoke(godot_dict, &exc); - UNHANDLED_EXCEPTION(exc); - - return *unbox<Dictionary *>(ret); -} - -MonoObject *Array_to_system_generic_list(const Array &p_array, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype) { - MonoType *elem_type = mono_reflection_type_get_type(p_elem_reftype); - - String ctor_desc = ":.ctor(System.Collections.Generic.IEnumerable`1<" + GDMonoUtils::get_type_desc(elem_type) + ">)"; - GDMonoMethod *ctor = p_class->get_method_with_desc(ctor_desc, true); - CRASH_COND(ctor == nullptr); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, nullptr); - - GDMonoClass *godot_array_class = GDMonoUtils::Marshal::make_generic_array_type(p_elem_reftype); - MonoObject *godot_array = GDMonoUtils::create_managed_from(p_array, godot_array_class); - - void *ctor_args[1] = { godot_array }; - - MonoException *exc = nullptr; - ctor->invoke_raw(mono_object, ctor_args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -Variant system_generic_list_to_Array_variant(MonoObject *p_obj, GDMonoClass *p_class, [[maybe_unused]] MonoReflectionType *p_elem_reftype) { - GDMonoMethod *to_array = p_class->get_method("ToArray", 0); - CRASH_COND(to_array == nullptr); - - MonoException *exc = nullptr; - MonoObject *array = to_array->invoke_raw(p_obj, nullptr, &exc); - UNHANDLED_EXCEPTION(exc); - - ERR_FAIL_NULL_V(array, Variant()); - - ManagedType type = ManagedType::from_class(mono_object_get_class(array)); - - bool result_is_array = type.type_encoding != MONO_TYPE_SZARRAY && type.type_encoding != MONO_TYPE_ARRAY; - ERR_FAIL_COND_V(result_is_array, Variant()); - - return mono_object_to_variant(array, type); -} - -MonoArray *Array_to_mono_array(const Array &p_array) { - int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), length); - - for (int i = 0; i < length; i++) { - MonoObject *boxed = variant_to_mono_object(p_array[i]); - mono_array_setref(ret, i, boxed); - } - - return ret; -} - -MonoArray *Array_to_mono_array(const Array &p_array, MonoClass *p_array_type_class) { - int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), p_array_type_class, length); - - for (int i = 0; i < length; i++) { - MonoObject *boxed = variant_to_mono_object(p_array[i]); - mono_array_setref(ret, i, boxed); - } - - return ret; -} - -Array mono_array_to_Array(MonoArray *p_array) { - Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_array, MonoObject *, i); - ret[i] = mono_object_to_variant(elem); - } - - return ret; -} - -MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array) { - 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), length); - - int32_t *dst = mono_array_addr(ret, int32_t, 0); - memcpy(dst, src, length * sizeof(int32_t)); - - return ret; -} - -PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array) { - PackedInt32Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - int32_t *dst = ret.ptrw(); - - const int32_t *src = mono_array_addr(p_array, int32_t, 0); - memcpy(dst, src, length * sizeof(int32_t)); - - 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 = mono_array_addr(ret, int64_t, 0); - memcpy(dst, src, length * sizeof(int64_t)); - - return ret; -} - -PackedInt64Array mono_array_to_PackedInt64Array(MonoArray *p_array) { - PackedInt64Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - int64_t *dst = ret.ptrw(); - - const int64_t *src = mono_array_addr(p_array, int64_t, 0); - memcpy(dst, src, length * sizeof(int64_t)); - - return ret; -} - -MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array) { - 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), length); - - uint8_t *dst = mono_array_addr(ret, uint8_t, 0); - memcpy(dst, src, length * sizeof(uint8_t)); - - return ret; -} - -PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array) { - PackedByteArray ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - uint8_t *dst = ret.ptrw(); - - const uint8_t *src = mono_array_addr(p_array, uint8_t, 0); - memcpy(dst, src, length * sizeof(uint8_t)); - - return ret; -} - -MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array) { - const float *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(float), length); - - float *dst = mono_array_addr(ret, float, 0); - memcpy(dst, src, length * sizeof(float)); - - return ret; -} - -PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array) { - PackedFloat32Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - float *dst = ret.ptrw(); - - const float *src = mono_array_addr(p_array, float, 0); - memcpy(dst, src, length * sizeof(float)); - - 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 = mono_array_addr(ret, double, 0); - memcpy(dst, src, length * sizeof(double)); - - return ret; -} - -PackedFloat64Array mono_array_to_PackedFloat64Array(MonoArray *p_array) { - PackedFloat64Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - double *dst = ret.ptrw(); - - const double *src = mono_array_addr(p_array, double, 0); - memcpy(dst, src, length * sizeof(double)); - - 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), length); - - for (int i = 0; i < length; i++) { - MonoString *boxed = mono_string_from_godot(r[i]); - mono_array_setref(ret, i, boxed); - } - - return ret; -} - -PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array) { - PackedStringArray ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - String *w = ret.ptrw(); - - for (int i = 0; i < length; i++) { - MonoString *elem = mono_array_get(p_array, MonoString *, i); - w[i] = mono_string_to_godot(elem); - } - - return ret; -} - -MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array) { - const Color *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), length); - - if constexpr (InteropLayout::MATCHES_Color) { - Color *dst = mono_array_addr(ret, Color, 0); - memcpy(dst, src, length * sizeof(Color)); - } else { - for (int i = 0; i < length; i++) { - M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i); - *raw = MARSHALLED_OUT(Color, src[i]); - } - } - - return ret; -} - -PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array) { - PackedColorArray ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - Color *dst = ret.ptrw(); - - if constexpr (InteropLayout::MATCHES_Color) { - const Color *src = mono_array_addr(p_array, Color, 0); - memcpy(dst, src, length * sizeof(Color)); - } else { - for (int i = 0; i < length; i++) { - dst[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i)); - } - } - - return ret; -} - -MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array) { - const Vector2 *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), length); - - if constexpr (InteropLayout::MATCHES_Vector2) { - Vector2 *dst = mono_array_addr(ret, Vector2, 0); - memcpy(dst, src, length * sizeof(Vector2)); - } else { - for (int i = 0; i < length; i++) { - M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i); - *raw = MARSHALLED_OUT(Vector2, src[i]); - } - } - - return ret; -} - -PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array) { - PackedVector2Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - Vector2 *dst = ret.ptrw(); - - if constexpr (InteropLayout::MATCHES_Vector2) { - const Vector2 *src = mono_array_addr(p_array, Vector2, 0); - memcpy(dst, src, length * sizeof(Vector2)); - } else { - for (int i = 0; i < length; i++) { - dst[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i)); - } - } - - return ret; -} - -MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array) { - const Vector3 *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), length); - - if constexpr (InteropLayout::MATCHES_Vector3) { - Vector3 *dst = mono_array_addr(ret, Vector3, 0); - memcpy(dst, src, length * sizeof(Vector3)); - } else { - for (int i = 0; i < length; i++) { - M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i); - *raw = MARSHALLED_OUT(Vector3, src[i]); - } - } - - return ret; -} - -PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array) { - PackedVector3Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - Vector3 *dst = ret.ptrw(); - - if constexpr (InteropLayout::MATCHES_Vector3) { - const Vector3 *src = mono_array_addr(p_array, Vector3, 0); - memcpy(dst, src, length * sizeof(Vector3)); - } else { - for (int i = 0; i < length; i++) { - dst[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i)); - } - } - - 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 = p_managed_callable.method_string_name - ? unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_callable.method_string_name)) - : nullptr; - 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 = p_managed_signal.name_string_name - ? unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_signal.name_string_name)) - : nullptr; - 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 deleted file mode 100644 index 51f11ab18a..0000000000 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ /dev/null @@ -1,605 +0,0 @@ -/*************************************************************************/ -/* gd_mono_marshal.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_MARSHAL_H -#define GD_MONO_MARSHAL_H - -#include "core/variant/variant.h" - -#include "../managed_callable.h" -#include "gd_mono.h" -#include "gd_mono_utils.h" - -namespace GDMonoMarshal { - -template <typename T> -T unbox(MonoObject *p_obj) { - return *(T *)mono_object_unbox(p_obj); -} - -template <typename T> -T *unbox_addr(MonoObject *p_obj) { - return (T *)mono_object_unbox(p_obj); -} - -#define BOX_DOUBLE(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(double), &x) -#define BOX_FLOAT(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(float), &x) -#define BOX_INT64(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int64_t), &x) -#define BOX_INT32(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int32_t), &x) -#define BOX_INT16(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int16_t), &x) -#define BOX_INT8(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int8_t), &x) -#define BOX_UINT64(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint64_t), &x) -#define BOX_UINT32(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint32_t), &x) -#define BOX_UINT16(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint16_t), &x) -#define BOX_UINT8(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), &x) -#define BOX_BOOLEAN(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(bool), &x) -#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, bool *r_nil_is_variant = nullptr); - -bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type); - -// String - -_FORCE_INLINE_ String mono_string_to_godot_not_null(MonoString *p_mono_string) { - char32_t *utf32 = (char32_t *)mono_string_to_utf32(p_mono_string); - String ret = String(utf32); - mono_free(utf32); - return ret; -} - -_FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) { - if (p_mono_string == nullptr) { - return String(); - } - - return mono_string_to_godot_not_null(p_mono_string); -} - -_FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) { - return mono_string_from_utf32((mono_unichar4 *)(p_string.get_data())); -} - -// Variant - -size_t variant_get_managed_unboxed_size(const ManagedType &p_type); -void *variant_to_managed_unboxed(const Variant &p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset); -MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type); - -MonoObject *variant_to_mono_object(const Variant &p_var); -MonoArray *variant_to_mono_array(const Variant &p_var, GDMonoClass *p_type_class); -MonoObject *variant_to_mono_object_of_class(const Variant &p_var, GDMonoClass *p_type_class); -MonoObject *variant_to_mono_object_of_genericinst(const Variant &p_var, GDMonoClass *p_type_class); -MonoString *variant_to_mono_string(const Variant &p_var); - -// These overloads were added to avoid passing a `const Variant *` to the `const Variant &` -// parameter. That would result in the `Variant(bool)` copy constructor being called as -// pointers are implicitly converted to bool. Implicit conversions are f-ing evil. - -_FORCE_INLINE_ void *variant_to_managed_unboxed(const Variant *p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset) { - return variant_to_managed_unboxed(*p_var, p_type, r_buffer, r_offset); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type) { - return variant_to_mono_object(*p_var, p_type); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant *p_var) { - return variant_to_mono_object(*p_var); -} -_FORCE_INLINE_ MonoArray *variant_to_mono_array(const Variant *p_var, GDMonoClass *p_type_class) { - return variant_to_mono_array(*p_var, p_type_class); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object_of_class(const Variant *p_var, GDMonoClass *p_type_class) { - return variant_to_mono_object_of_class(*p_var, p_type_class); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object_of_genericinst(const Variant *p_var, GDMonoClass *p_type_class) { - return variant_to_mono_object_of_genericinst(*p_var, p_type_class); -} -_FORCE_INLINE_ MonoString *variant_to_mono_string(const Variant *p_var) { - return variant_to_mono_string(*p_var); -} - -Variant mono_object_to_variant(MonoObject *p_obj); -Variant mono_object_to_variant(MonoObject *p_obj, const ManagedType &p_type); -Variant mono_object_to_variant_no_err(MonoObject *p_obj, const ManagedType &p_type); - -/// Tries to convert the MonoObject* to Variant and then convert the Variant to String. -/// If the MonoObject* cannot be converted to Variant, then 'ToString()' is called instead. -String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc); - -// System.Collections.Generic - -MonoObject *Dictionary_to_system_generic_dict(const Dictionary &p_dict, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); -Dictionary system_generic_dict_to_Dictionary(MonoObject *p_obj, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); - -MonoObject *Array_to_system_generic_list(const Array &p_array, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype); -Variant system_generic_list_to_Array_variant(MonoObject *p_obj, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype); - -// Array - -MonoArray *Array_to_mono_array(const Array &p_array); -MonoArray *Array_to_mono_array(const Array &p_array, MonoClass *p_array_type_class); -Array mono_array_to_Array(MonoArray *p_array); - -// PackedInt32Array - -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); -PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array); - -// PackedFloat32Array - -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); -PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array); - -// PackedColorArray - -MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array); -PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array); - -// PackedVector2Array - -MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array); -PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array); - -// PackedVector3Array - -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 = nullptr; - MonoObject *method_string_name = nullptr; - MonoDelegate *delegate = nullptr; -}; - -struct M_SignalInfo { - MonoObject *owner = nullptr; - MonoObject *name_string_name = nullptr; -}; - -#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)), - -#ifdef REAL_T_IS_DOUBLE - MATCHES_real_t = (sizeof(real_t) == sizeof(uint64_t)), -#else - MATCHES_real_t = (sizeof(real_t) == sizeof(uint32_t)), -#endif - - MATCHES_Vector2 = (MATCHES_real_t && (sizeof(Vector2) == (sizeof(real_t) * 2)) && - offsetof(Vector2, x) == (sizeof(real_t) * 0) && - offsetof(Vector2, y) == (sizeof(real_t) * 1)), - - 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)) && - offsetof(Vector3, x) == (sizeof(real_t) * 0) && - offsetof(Vector3, y) == (sizeof(real_t) * 1) && - offsetof(Vector3, z) == (sizeof(real_t) * 2)), - - MATCHES_Vector4 = (MATCHES_real_t && (sizeof(Vector4) == (sizeof(real_t) * 4)) && - offsetof(Vector4, x) == (sizeof(real_t) * 0) && - offsetof(Vector4, y) == (sizeof(real_t) * 1) && - offsetof(Vector4, z) == (sizeof(real_t) * 2) && - offsetof(Vector4, w) == (sizeof(real_t) * 3)), - - MATCHES_Vector4i = (MATCHES_int && (sizeof(Vector4i) == (sizeof(int32_t) * 4)) && - offsetof(Vector4i, x) == (sizeof(int32_t) * 0) && - offsetof(Vector4i, y) == (sizeof(int32_t) * 1) && - offsetof(Vector4i, z) == (sizeof(int32_t) * 2) && - offsetof(Vector4i, w) == (sizeof(int32_t) * 3)), - - 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_Quaternion = (MATCHES_real_t && (sizeof(Quaternion) == (sizeof(real_t) * 4)) && - offsetof(Quaternion, x) == (sizeof(real_t) * 0) && - offsetof(Quaternion, y) == (sizeof(real_t) * 1) && - offsetof(Quaternion, z) == (sizeof(real_t) * 2) && - offsetof(Quaternion, w) == (sizeof(real_t) * 3)), - - MATCHES_Transform3D = (MATCHES_Basis && MATCHES_Vector3 && (sizeof(Transform3D) == (sizeof(Basis) + sizeof(Vector3))) && - offsetof(Transform3D, basis) == 0 && - offsetof(Transform3D, origin) == sizeof(Basis)), - - MATCHES_Projection = (MATCHES_Vector4 && (sizeof(Projection) == (sizeof(Vector4) * 4))), - - MATCHES_AABB = (MATCHES_Vector3 && (sizeof(AABB) == (sizeof(Vector3) * 2)) && - offsetof(AABB, position) == (sizeof(Vector3) * 0) && - offsetof(AABB, size) == (sizeof(Vector3) * 1)), - - MATCHES_Color = (MATCHES_float && (sizeof(Color) == (sizeof(float) * 4)) && - offsetof(Color, r) == (sizeof(float) * 0) && - offsetof(Color, g) == (sizeof(float) * 1) && - offsetof(Color, b) == (sizeof(float) * 2) && - offsetof(Color, a) == (sizeof(float) * 3)), - - MATCHES_Plane = (MATCHES_Vector3 && MATCHES_real_t && (sizeof(Plane) == (sizeof(Vector3) + sizeof(real_t))) && - offsetof(Plane, normal) == 0 && - offsetof(Plane, d) == sizeof(Vector3)) -}; - -// 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 */ -static_assert(MATCHES_Vector2 && MATCHES_Rect2 && MATCHES_Transform2D && MATCHES_Vector3 && MATCHES_Vector4 && - MATCHES_Basis && MATCHES_Quaternion && MATCHES_Transform3D && MATCHES_Projection && MATCHES_AABB && MATCHES_Color && - MATCHES_Plane && MATCHES_Vector2i && MATCHES_Rect2i && MATCHES_Vector3i && MATCHES_Vector4i); -/* clang-format on */ -#endif -} // namespace InteropLayout - -#pragma pack(push, 1) - -struct M_Vector2 { - real_t x, y; - - static _FORCE_INLINE_ Vector2 convert_to(const M_Vector2 &p_from) { - return Vector2(p_from.x, p_from.y); - } - - static _FORCE_INLINE_ M_Vector2 convert_from(const Vector2 &p_from) { - M_Vector2 ret = { p_from.x, p_from.y }; - return ret; - } -}; - -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; - - static _FORCE_INLINE_ Rect2 convert_to(const M_Rect2 &p_from) { - return Rect2(M_Vector2::convert_to(p_from.position), - M_Vector2::convert_to(p_from.size)); - } - - static _FORCE_INLINE_ M_Rect2 convert_from(const Rect2 &p_from) { - M_Rect2 ret = { M_Vector2::convert_from(p_from.position), M_Vector2::convert_from(p_from.size) }; - return ret; - } -}; - -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]; - - static _FORCE_INLINE_ Transform2D convert_to(const M_Transform2D &p_from) { - return Transform2D(p_from.elements[0].x, p_from.elements[0].y, - p_from.elements[1].x, p_from.elements[1].y, - p_from.elements[2].x, p_from.elements[2].y); - } - - static _FORCE_INLINE_ M_Transform2D convert_from(const Transform2D &p_from) { - M_Transform2D ret = { - M_Vector2::convert_from(p_from.columns[0]), - M_Vector2::convert_from(p_from.columns[1]), - M_Vector2::convert_from(p_from.columns[2]) - }; - return ret; - } -}; - -struct M_Vector3 { - real_t x, y, z; - - static _FORCE_INLINE_ Vector3 convert_to(const M_Vector3 &p_from) { - return Vector3(p_from.x, p_from.y, p_from.z); - } - - static _FORCE_INLINE_ M_Vector3 convert_from(const Vector3 &p_from) { - M_Vector3 ret = { p_from.x, p_from.y, p_from.z }; - return ret; - } -}; - -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_Vector4 { - real_t x, y, z, w; - - static _FORCE_INLINE_ Vector4 convert_to(const M_Vector4 &p_from) { - return Vector4(p_from.x, p_from.y, p_from.z, p_from.w); - } - - static _FORCE_INLINE_ M_Vector4 convert_from(const Vector4 &p_from) { - M_Vector4 ret = { p_from.x, p_from.y, p_from.z, p_from.w }; - return ret; - } -}; - -struct M_Vector4i { - int32_t x, y, z, w; - - static _FORCE_INLINE_ Vector4i convert_to(const M_Vector4i &p_from) { - return Vector4i(p_from.x, p_from.y, p_from.z, p_from.w); - } - - static _FORCE_INLINE_ M_Vector4i convert_from(const Vector4i &p_from) { - M_Vector4i ret = { p_from.x, p_from.y, p_from.z, p_from.w }; - return ret; - } -}; - -struct M_Basis { - M_Vector3 elements[3]; - - static _FORCE_INLINE_ Basis convert_to(const M_Basis &p_from) { - return Basis(M_Vector3::convert_to(p_from.elements[0]), - M_Vector3::convert_to(p_from.elements[1]), - M_Vector3::convert_to(p_from.elements[2])); - } - - static _FORCE_INLINE_ M_Basis convert_from(const Basis &p_from) { - M_Basis ret = { - M_Vector3::convert_from(p_from.rows[0]), - M_Vector3::convert_from(p_from.rows[1]), - M_Vector3::convert_from(p_from.rows[2]) - }; - return ret; - } -}; - -struct M_Quaternion { - real_t x, y, z, w; - - static _FORCE_INLINE_ Quaternion convert_to(const M_Quaternion &p_from) { - return Quaternion(p_from.x, p_from.y, p_from.z, p_from.w); - } - - static _FORCE_INLINE_ M_Quaternion convert_from(const Quaternion &p_from) { - M_Quaternion ret = { p_from.x, p_from.y, p_from.z, p_from.w }; - return ret; - } -}; - -struct M_Transform3D { - M_Basis basis; - M_Vector3 origin; - - static _FORCE_INLINE_ Transform3D convert_to(const M_Transform3D &p_from) { - return Transform3D(M_Basis::convert_to(p_from.basis), M_Vector3::convert_to(p_from.origin)); - } - - static _FORCE_INLINE_ M_Transform3D convert_from(const Transform3D &p_from) { - M_Transform3D ret = { M_Basis::convert_from(p_from.basis), M_Vector3::convert_from(p_from.origin) }; - return ret; - } -}; - -struct M_Projection { - M_Vector4 vec1; - M_Vector4 vec2; - M_Vector4 vec3; - M_Vector4 vec4; - - static _FORCE_INLINE_ Projection convert_to(const M_Projection &p_from) { - return Projection(M_Vector4::convert_to(p_from.vec1), M_Vector4::convert_to(p_from.vec2), M_Vector4::convert_to(p_from.vec3), M_Vector4::convert_to(p_from.vec4)); - } - - static _FORCE_INLINE_ M_Projection convert_from(const Projection &p_from) { - M_Projection ret = { M_Vector4::convert_from(p_from.matrix[0]), M_Vector4::convert_from(p_from.matrix[1]), M_Vector4::convert_from(p_from.matrix[2]), M_Vector4::convert_from(p_from.matrix[3]) }; - return ret; - } -}; - -struct M_AABB { - M_Vector3 position; - M_Vector3 size; - - static _FORCE_INLINE_ AABB convert_to(const M_AABB &p_from) { - return AABB(M_Vector3::convert_to(p_from.position), M_Vector3::convert_to(p_from.size)); - } - - static _FORCE_INLINE_ M_AABB convert_from(const AABB &p_from) { - M_AABB ret = { M_Vector3::convert_from(p_from.position), M_Vector3::convert_from(p_from.size) }; - return ret; - } -}; - -struct M_Color { - float r, g, b, a; - - static _FORCE_INLINE_ Color convert_to(const M_Color &p_from) { - return Color(p_from.r, p_from.g, p_from.b, p_from.a); - } - - static _FORCE_INLINE_ M_Color convert_from(const Color &p_from) { - M_Color ret = { p_from.r, p_from.g, p_from.b, p_from.a }; - return ret; - } -}; - -struct M_Plane { - M_Vector3 normal; - real_t d; - - static _FORCE_INLINE_ Plane convert_to(const M_Plane &p_from) { - return Plane(M_Vector3::convert_to(p_from.normal), p_from.d); - } - - static _FORCE_INLINE_ M_Plane convert_from(const Plane &p_from) { - M_Plane ret = { M_Vector3::convert_from(p_from.normal), p_from.d }; - return ret; - } -}; - -#pragma pack(pop) - -#define DECL_TYPE_MARSHAL_TEMPLATES(m_type) \ - template <int> \ - _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl(const M_##m_type *p_from); \ - \ - template <> \ - _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<0>(const M_##m_type *p_from) { \ - return M_##m_type::convert_to(*p_from); \ - } \ - \ - template <> \ - _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<1>(const M_##m_type *p_from) { \ - return *reinterpret_cast<const m_type *>(p_from); \ - } \ - \ - _FORCE_INLINE_ m_type marshalled_in_##m_type(const M_##m_type *p_from) { \ - return marshalled_in_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \ - } \ - \ - template <int> \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl(const m_type &p_from); \ - \ - template <> \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<0>(const m_type &p_from) { \ - return M_##m_type::convert_from(p_from); \ - } \ - \ - template <> \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<1>(const m_type &p_from) { \ - return *reinterpret_cast<const M_##m_type *>(&p_from); \ - } \ - \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type(const m_type &p_from) { \ - return marshalled_out_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \ - } - -DECL_TYPE_MARSHAL_TEMPLATES(Vector2) -DECL_TYPE_MARSHAL_TEMPLATES(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(Vector4) -DECL_TYPE_MARSHAL_TEMPLATES(Vector4i) -DECL_TYPE_MARSHAL_TEMPLATES(Quaternion) -DECL_TYPE_MARSHAL_TEMPLATES(Transform3D) -DECL_TYPE_MARSHAL_TEMPLATES(Projection) -DECL_TYPE_MARSHAL_TEMPLATES(AABB) -DECL_TYPE_MARSHAL_TEMPLATES(Color) -DECL_TYPE_MARSHAL_TEMPLATES(Plane) - -#define MARSHALLED_IN(m_type, m_from_ptr) (GDMonoMarshal::marshalled_in_##m_type(m_from_ptr)) -#define MARSHALLED_OUT(m_type, m_from) (GDMonoMarshal::marshalled_out_##m_type(m_from)) -} // namespace GDMonoMarshal - -#endif // GD_MONO_MARSHAL_H diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp deleted file mode 100644 index 6734b44783..0000000000 --- a/modules/mono/mono_gd/gd_mono_method.cpp +++ /dev/null @@ -1,296 +0,0 @@ -/*************************************************************************/ -/* gd_mono_method.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_method.h" - -#include <mono/metadata/attrdefs.h> -#include <mono/metadata/debug-helpers.h> - -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" - -void GDMonoMethod::_update_signature() { - // Apparently MonoMethodSignature needs not to be freed. - // mono_method_signature caches the result, we don't need to cache it ourselves. - - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - _update_signature(method_sig); -} - -void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) { - params_count = mono_signature_get_param_count(p_method_sig); - - MonoType *ret_type = mono_signature_get_return_type(p_method_sig); - if (ret_type) { - return_type.type_encoding = mono_type_get_type(ret_type); - - if (return_type.type_encoding != MONO_TYPE_VOID) { - MonoClass *ret_type_class = mono_class_from_mono_type(ret_type); - return_type.type_class = GDMono::get_singleton()->get_class(ret_type_class); - } - } - - void *iter = nullptr; - MonoType *param_raw_type; - 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); - - MonoClass *param_type_class = mono_class_from_mono_type(param_raw_type); - param_type.type_class = GDMono::get_singleton()->get_class(param_type_class); - - param_types.push_back(param_type); - } - - // clear the cache - method_info_fetched = false; - method_info = MethodInfo(); - - for (int i = 0; i < params_count; i++) { - params_buffer_size += GDMonoMarshal::variant_get_managed_unboxed_size(param_types[i]); - } -} - -GDMonoClass *GDMonoMethod::get_enclosing_class() const { - return GDMono::get_singleton()->get_class(mono_method_get_class(mono_method)); -} - -bool GDMonoMethod::is_static() { - return mono_method_get_flags(mono_method, nullptr) & MONO_METHOD_ATTR_STATIC; -} - -IMonoClassMember::Visibility GDMonoMethod::get_visibility() { - 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: - return IMonoClassMember::PROTECTED_AND_INTERNAL; - case MONO_METHOD_ATTR_ASSEM: - return IMonoClassMember::INTERNAL; - case MONO_METHOD_ATTR_FAMILY: - return IMonoClassMember::PROTECTED; - case MONO_METHOD_ATTR_PUBLIC: - return IMonoClassMember::PUBLIC; - default: - ERR_FAIL_V(IMonoClassMember::PRIVATE); - } -} - -MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc) const { - MonoException *exc = nullptr; - MonoObject *ret; - - if (params_count > 0) { - void **params = (void **)alloca(params_count * sizeof(void *)); - uint8_t *buffer = (uint8_t *)alloca(params_buffer_size); - unsigned int offset = 0; - - for (int i = 0; i < params_count; i++) { - params[i] = GDMonoMarshal::variant_to_managed_unboxed(p_params[i], param_types[i], buffer + offset, offset); - } - - ret = GDMonoUtils::runtime_invoke(mono_method, p_object, params, &exc); - } else { - ret = GDMonoUtils::runtime_invoke(mono_method, p_object, nullptr, &exc); - } - - 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) 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) const { - MonoException *exc = nullptr; - MonoObject *ret = GDMonoUtils::runtime_invoke(mono_method, p_object, p_params, &exc); - - if (exc) { - ret = nullptr; - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } - - return ret; -} - -bool GDMonoMethod::has_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, false); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoMethod::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, nullptr); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoMethod::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - attributes = mono_custom_attrs_from_method(mono_method); - attrs_fetched = true; -} - -String GDMonoMethod::get_full_name(bool p_signature) const { - char *res = mono_method_full_name(mono_method, p_signature); - String full_name(res); - mono_free(res); - return full_name; -} - -String GDMonoMethod::get_full_name_no_class() const { - String res; - - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - - char *ret_str = mono_type_full_name(mono_signature_get_return_type(method_sig)); - res += ret_str; - mono_free(ret_str); - - res += " "; - res += name; - res += "("; - - char *sig_desc = mono_signature_get_desc(method_sig, true); - res += sig_desc; - mono_free(sig_desc); - - res += ")"; - - return res; -} - -String GDMonoMethod::get_ret_type_full_name() const { - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - char *ret_str = mono_type_full_name(mono_signature_get_return_type(method_sig)); - String res = ret_str; - mono_free(ret_str); - return res; -} - -String GDMonoMethod::get_signature_desc(bool p_namespaces) const { - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - char *sig_desc = mono_signature_get_desc(method_sig, p_namespaces); - String res = sig_desc; - mono_free(sig_desc); - return res; -} - -void GDMonoMethod::get_parameter_names(Vector<StringName> &names) const { - if (params_count > 0) { - const char **_names = memnew_arr(const char *, params_count); - mono_method_get_param_names(mono_method, _names); - for (int i = 0; i < params_count; ++i) { - names.push_back(StringName(_names[i])); - } - memdelete_arr(_names); - } -} - -void GDMonoMethod::get_parameter_types(Vector<ManagedType> &types) const { - for (int i = 0; i < params_count; ++i) { - types.push_back(param_types[i]); - } -} - -const MethodInfo &GDMonoMethod::get_method_info() { - if (!method_info_fetched) { - method_info.name = name; - - bool nil_is_variant = false; - method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type, &nil_is_variant), ""); - if (method_info.return_val.type == Variant::NIL && nil_is_variant) { - 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) { - 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 - - method_info_fetched = true; - } - - return method_info; -} - -GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) : - name(p_name), mono_method(p_method) { - _update_signature(); -} - -GDMonoMethod::~GDMonoMethod() { - if (attributes) { - mono_custom_attrs_free(attributes); - } -} diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h deleted file mode 100644 index be11ef5bfe..0000000000 --- a/modules/mono/mono_gd/gd_mono_method.h +++ /dev/null @@ -1,97 +0,0 @@ -/*************************************************************************/ -/* gd_mono_method.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_METHOD_H -#define GD_MONO_METHOD_H - -#include "gd_mono.h" -#include "gd_mono_header.h" -#include "i_mono_class_member.h" - -class GDMonoMethod : public IMonoClassMember { - StringName name; - - uint16_t params_count; - unsigned int params_buffer_size = 0; - ManagedType return_type; - Vector<ManagedType> param_types; - - bool method_info_fetched = false; - MethodInfo method_info; - - bool attrs_fetched = false; - MonoCustomAttrInfo *attributes = nullptr; - - void _update_signature(); - void _update_signature(MonoMethodSignature *p_method_sig); - - friend class GDMonoClass; - - MonoMethod *mono_method = nullptr; - -public: - virtual GDMonoClass *get_enclosing_class() const final; - - virtual MemberType get_member_type() const final { return MEMBER_TYPE_METHOD; } - - virtual StringName get_name() const final { return name; } - - virtual bool is_static() final; - - virtual Visibility get_visibility() 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() const { return mono_method; } - - _FORCE_INLINE_ uint16_t 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 = 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; - String get_ret_type_full_name() const; - String get_signature_desc(bool p_namespaces = false) const; - - void get_parameter_names(Vector<StringName> &names) const; - void get_parameter_types(Vector<ManagedType> &types) const; - - const MethodInfo &get_method_info(); - - GDMonoMethod(StringName p_name, MonoMethod *p_method); - ~GDMonoMethod(); -}; - -#endif // GD_MONO_METHOD_H diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h deleted file mode 100644 index 0180dee3ea..0000000000 --- a/modules/mono/mono_gd/gd_mono_method_thunk.h +++ /dev/null @@ -1,320 +0,0 @@ -/*************************************************************************/ -/* gd_mono_method_thunk.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_METHOD_THUNK_H -#define GD_MONO_METHOD_THUNK_H - -#include <type_traits> - -#include "gd_mono_class.h" -#include "gd_mono_header.h" -#include "gd_mono_marshal.h" -#include "gd_mono_method.h" -#include "gd_mono_utils.h" - -#if !defined(JAVASCRIPT_ENABLED) && !defined(IOS_ENABLED) -#define HAVE_METHOD_THUNKS -#endif - -#ifdef HAVE_METHOD_THUNKS - -template <class... ParamTypes> -struct GDMonoMethodThunk { - typedef void(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); - - M mono_method_thunk = nullptr; - -public: - _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_method_thunk(p_args..., r_exc); - GD_MONO_END_RUNTIME_INVOKE; - } - - _FORCE_INLINE_ bool is_null() { - return mono_method_thunk == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method_thunk = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - 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()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); - } - - GDMonoMethodThunk() {} - - explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { - set_from_method(p_mono_method); - } -}; - -template <class R, class... ParamTypes> -struct GDMonoMethodThunkR { - typedef R(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); - - M mono_method_thunk = nullptr; - -public: - _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = mono_method_thunk(p_args..., r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return r; - } - - _FORCE_INLINE_ bool is_null() { - return mono_method_thunk == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method_thunk = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - 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()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); - } - - GDMonoMethodThunkR() {} - - explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == nullptr); -#endif - mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); - } -}; - -#else - -template <unsigned int ThunkParamCount, class P1, class... ParamTypes> -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(nullptr, args, r_exc); - } else { - void *args[ThunkParamCount] = { p_args... }; - p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc); - } - } -}; - -template <unsigned int ThunkParamCount, class... ParamTypes> -struct VariadicInvokeMonoMethod { - static void invoke(GDMonoMethod *p_mono_method, ParamTypes... p_args, MonoException **r_exc) { - VariadicInvokeMonoMethodImpl<ThunkParamCount, ParamTypes...>::invoke(p_mono_method, p_args..., r_exc); - } -}; - -template <> -struct VariadicInvokeMonoMethod<0> { - static void invoke(GDMonoMethod *p_mono_method, MonoException **r_exc) { -#ifdef DEBUG_ENABLED - CRASH_COND(!p_mono_method->is_static()); -#endif - p_mono_method->invoke_raw(nullptr, nullptr, r_exc); - } -}; - -template <class P1> -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(nullptr, args, r_exc); - } else { - p_mono_method->invoke_raw((MonoObject *)p_arg1, nullptr, r_exc); - } - } -}; - -template <class R> -R unbox_if_needed(MonoObject *p_val, const ManagedType &, typename std::enable_if<!std::is_pointer<R>::value>::type * = 0) { - return GDMonoMarshal::unbox<R>(p_val); -} - -template <class R> -R unbox_if_needed(MonoObject *p_val, const ManagedType &p_type, typename std::enable_if<std::is_pointer<R>::value>::type * = 0) { - if (mono_class_is_valuetype(p_type.type_class->get_mono_ptr())) { - return GDMonoMarshal::unbox<R>(p_val); - } else { - // If it's not a value type, we assume 'R' is a pointer to 'MonoObject' or a compatible type, like 'MonoException'. - return (R)p_val; - } -} - -template <unsigned int ThunkParamCount, class R, class P1, class... ParamTypes> -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(nullptr, args, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } else { - void *args[ThunkParamCount] = { p_args... }; - MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } - } -}; - -template <unsigned int ThunkParamCount, class R, class... ParamTypes> -struct VariadicInvokeMonoMethodR { - static R invoke(GDMonoMethod *p_mono_method, ParamTypes... p_args, MonoException **r_exc) { - return VariadicInvokeMonoMethodRImpl<ThunkParamCount, R, ParamTypes...>::invoke(p_mono_method, p_args..., r_exc); - } -}; - -template <class R> -struct VariadicInvokeMonoMethodR<0, R> { - static R invoke(GDMonoMethod *p_mono_method, MonoException **r_exc) { -#ifdef DEBUG_ENABLED - CRASH_COND(!p_mono_method->is_static()); -#endif - MonoObject *r = p_mono_method->invoke_raw(nullptr, nullptr, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } -}; - -template <class R, class P1> -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(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, nullptr, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } - } -}; - -template <class... ParamTypes> -struct GDMonoMethodThunk { - GDMonoMethod *mono_method = nullptr; - -public: - _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { - VariadicInvokeMonoMethod<sizeof...(ParamTypes), ParamTypes...>::invoke(mono_method, p_args..., r_exc); - } - - _FORCE_INLINE_ bool is_null() { - return mono_method == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - 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()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method = p_mono_method; - } - - GDMonoMethodThunk() {} - - explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { - set_from_method(p_mono_method); - } -}; - -template <class R, class... ParamTypes> -struct GDMonoMethodThunkR { - GDMonoMethod *mono_method = nullptr; - -public: - _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { - return VariadicInvokeMonoMethodR<sizeof...(ParamTypes), R, ParamTypes...>::invoke(mono_method, p_args..., r_exc); - } - - _FORCE_INLINE_ bool is_null() { - return mono_method == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - 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()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method = p_mono_method; - } - - GDMonoMethodThunkR() {} - - explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { - set_from_method(p_mono_method); - } -}; - -#endif - -#endif // GD_MONO_METHOD_THUNK_H diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp deleted file mode 100644 index c9775ae9cb..0000000000 --- a/modules/mono/mono_gd/gd_mono_property.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/*************************************************************************/ -/* gd_mono_property.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_property.h" - -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" - -#include <mono/metadata/attrdefs.h> - -GDMonoProperty::GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_owner) { - owner = p_owner; - mono_property = p_mono_property; - name = String::utf8(mono_property_get_name(mono_property)); - - MonoMethod *prop_method = mono_property_get_get_method(mono_property); - - if (prop_method) { - MonoMethodSignature *getter_sig = mono_method_signature(prop_method); - - MonoType *ret_type = mono_signature_get_return_type(getter_sig); - - type.type_encoding = mono_type_get_type(ret_type); - MonoClass *ret_type_class = mono_class_from_mono_type(ret_type); - type.type_class = GDMono::get_singleton()->get_class(ret_type_class); - } else { - prop_method = mono_property_get_set_method(mono_property); - - MonoMethodSignature *setter_sig = mono_method_signature(prop_method); - - void *iter = nullptr; - MonoType *param_raw_type = mono_signature_get_params(setter_sig, &iter); - - type.type_encoding = mono_type_get_type(param_raw_type); - MonoClass *param_type_class = mono_class_from_mono_type(param_raw_type); - type.type_class = GDMono::get_singleton()->get_class(param_type_class); - } - - param_buffer_size = GDMonoMarshal::variant_get_managed_unboxed_size(type); - - attrs_fetched = false; - attributes = nullptr; -} - -GDMonoProperty::~GDMonoProperty() { - if (attributes) { - mono_custom_attrs_free(attributes); - } -} - -bool GDMonoProperty::is_static() { - MonoMethod *prop_method = mono_property_get_get_method(mono_property); - if (prop_method == nullptr) { - prop_method = mono_property_get_set_method(mono_property); - } - return mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_STATIC; -} - -IMonoClassMember::Visibility GDMonoProperty::get_visibility() { - MonoMethod *prop_method = mono_property_get_get_method(mono_property); - if (prop_method == nullptr) { - prop_method = mono_property_get_set_method(mono_property); - } - - switch (mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) { - case MONO_METHOD_ATTR_PRIVATE: - return IMonoClassMember::PRIVATE; - case MONO_METHOD_ATTR_FAM_AND_ASSEM: - return IMonoClassMember::PROTECTED_AND_INTERNAL; - case MONO_METHOD_ATTR_ASSEM: - return IMonoClassMember::INTERNAL; - case MONO_METHOD_ATTR_FAMILY: - return IMonoClassMember::PROTECTED; - case MONO_METHOD_ATTR_PUBLIC: - return IMonoClassMember::PUBLIC; - default: - ERR_FAIL_V(IMonoClassMember::PRIVATE); - } -} - -bool GDMonoProperty::has_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, false); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoProperty::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, nullptr); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoProperty::fetch_attributes() { - 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) != nullptr; -} - -bool GDMonoProperty::has_setter() { - return mono_property_get_set_method(mono_property) != nullptr; -} - -void GDMonoProperty::set_value_from_variant(MonoObject *p_object, const Variant &p_value, MonoException **r_exc) { - uint8_t *buffer = (uint8_t *)alloca(param_buffer_size); - unsigned int offset = 0; - - void *params[1] = { - GDMonoMarshal::variant_to_managed_unboxed(p_value, type, buffer, offset) - }; - -#ifdef DEBUG_ENABLED - CRASH_COND(offset != param_buffer_size); -#endif - - MonoException *exc = nullptr; - GDMonoUtils::property_set_value(mono_property, p_object, params, &exc); - if (exc) { - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } -} - -MonoObject *GDMonoProperty::get_value(MonoObject *p_object, MonoException **r_exc) { - MonoException *exc = nullptr; - MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, nullptr, &exc); - - if (exc) { - ret = nullptr; - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } - - return ret; -} - -bool GDMonoProperty::get_bool_value(MonoObject *p_object) { - return (bool)GDMonoMarshal::unbox<MonoBoolean>(get_value(p_object)); -} - -int GDMonoProperty::get_int_value(MonoObject *p_object) { - return GDMonoMarshal::unbox<int32_t>(get_value(p_object)); -} - -String GDMonoProperty::get_string_value(MonoObject *p_object) { - MonoObject *val = get_value(p_object); - return GDMonoMarshal::mono_string_to_godot((MonoString *)val); -} diff --git a/modules/mono/mono_gd/gd_mono_property.h b/modules/mono/mono_gd/gd_mono_property.h deleted file mode 100644 index 6fc681aeb5..0000000000 --- a/modules/mono/mono_gd/gd_mono_property.h +++ /dev/null @@ -1,80 +0,0 @@ -/*************************************************************************/ -/* gd_mono_property.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_PROPERTY_H -#define GD_MONO_PROPERTY_H - -#include "gd_mono.h" -#include "gd_mono_header.h" -#include "i_mono_class_member.h" - -class GDMonoProperty : public IMonoClassMember { - GDMonoClass *owner = nullptr; - MonoProperty *mono_property = nullptr; - - StringName name; - ManagedType type; - - bool attrs_fetched; - MonoCustomAttrInfo *attributes = nullptr; - - unsigned int param_buffer_size; - -public: - virtual GDMonoClass *get_enclosing_class() const final { return owner; } - - virtual MemberType get_member_type() const final { return MEMBER_TYPE_PROPERTY; } - - virtual StringName get_name() const final { return name; } - - virtual bool is_static() final; - virtual Visibility get_visibility() 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(); - bool has_setter(); - - _FORCE_INLINE_ ManagedType get_type() const { return type; } - - void set_value_from_variant(MonoObject *p_object, const Variant &p_value, 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); - String get_string_value(MonoObject *p_object); - - GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_owner); - ~GDMonoProperty(); -}; - -#endif // GD_MONO_PROPERTY_H diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp deleted file mode 100644 index 1983d6ebe2..0000000000 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ /dev/null @@ -1,677 +0,0 @@ -/*************************************************************************/ -/* gd_mono_utils.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_utils.h" - -#include <mono/metadata/debug-helpers.h> -#include <mono/metadata/exception.h> - -#include "core/debugger/engine_debugger.h" -#include "core/debugger/script_debugger.h" -#include "core/io/dir_access.h" -#include "core/object/ref_counted.h" -#include "core/os/mutex.h" -#include "core/os/os.h" - -#ifdef TOOLS_ENABLED -#include "editor/debugger/editor_debugger_node.h" -#endif - -#include "../csharp_script.h" -#include "../utils/macros.h" -#include "gd_mono.h" -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" - -namespace GDMonoUtils { - -MonoObject *unmanaged_get_managed(Object *unmanaged) { - if (!unmanaged) { - return nullptr; - } - - if (unmanaged->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance()); - - if (cs_instance) { - return cs_instance->get_mono_object(); - } - } - - // If the owner does not have a CSharpInstance... - - void *data = CSharpLanguage::get_instance_binding(unmanaged); - ERR_FAIL_NULL_V(data, nullptr); - CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value(); - ERR_FAIL_COND_V(!script_binding.inited, nullptr); - - MonoGCHandleData &gchandle = script_binding.gchandle; - - MonoObject *target = gchandle.get_target(); - - if (target) { - return target; - } - - CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); - - // Create a new one - -#ifdef DEBUG_ENABLED - CRASH_COND(script_binding.type_name == StringName()); - 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, nullptr); - - gchandle = MonoGCHandleData::new_strong_handle(mono_object); - - // Tie managed to unmanaged - RefCounted *rc = Object::cast_to<RefCounted>(unmanaged); - - if (rc) { - // Unsafe refcount increment. The managed instance also counts as a reference. - // This way if the unmanaged world has no references to our owner - // but the managed instance is alive, the refcount will be 1 instead of 0. - // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) - rc->reference(); - CSharpLanguage::get_singleton()->post_unsafe_reference(rc); - } - - return mono_object; -} - -void set_main_thread(MonoThread *p_thread) { - mono_thread_set_main(p_thread); -} - -MonoThread *attach_current_thread() { - 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()); -#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; -} - -void detach_current_thread() { - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - MonoThread *mono_thread = mono_thread_current(); - ERR_FAIL_NULL(mono_thread); - mono_thread_detach(mono_thread); -} - -void detach_current_thread(MonoThread *p_mono_thread) { - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - ERR_FAIL_NULL(p_mono_thread); - mono_thread_detach(p_mono_thread); -} - -MonoThread *get_current_thread() { - return mono_thread_current(); -} - -bool is_thread_attached() { - 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, 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) { - return GDMono::get_singleton()->get_class(mono_object_get_class(p_object)); -} - -GDMonoClass *type_get_proxy_class(const StringName &p_type) { - String class_name = p_type; - - if (class_name[0] == '_') { - class_name = class_name.substr(1, class_name.length()); - } - - GDMonoClass *klass = GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); - - if (klass && klass->is_static()) { - // A static class means this is a Godot singleton class. If an instance is needed we use Godot.Object. - return GDMonoCache::cached_data.class_GodotObject; - } - -#ifdef TOOLS_ENABLED - if (!klass) { - return GDMono::get_singleton()->get_editor_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); - } -#endif - - return klass; -} - -GDMonoClass *get_class_native_base(GDMonoClass *p_class) { - GDMonoClass *klass = p_class; - - do { - const GDMonoAssembly *assembly = klass->get_assembly(); - - if (assembly == GDMono::get_singleton()->get_core_api_assembly()) { - return klass; - } -#ifdef TOOLS_ENABLED - if (assembly == GDMono::get_singleton()->get_editor_api_assembly()) { - return klass; - } -#endif - } while ((klass = klass->get_parent_class()) != nullptr); - - 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, nullptr, - "Type inherits from native type '" + p_native + "', so it can't be instantiated 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, nullptr); - - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); - - // Construct - GDMonoUtils::runtime_object_init(mono_object, p_class); - - 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, nullptr); - - // Construct - GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(NodePath)); - - CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from))); - - return mono_object; -} - -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, nullptr); - - // Construct - GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(RID)); - - CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from))); - - return mono_object; -} - -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, nullptr); - - // Search constructor that takes a pointer as parameter - MonoMethod *m; - 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 = 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; - } - } - } - - CRASH_COND(m == nullptr); - - Array *new_array = memnew(Array(p_from)); - void *args[1] = { &new_array }; - - MonoException *exc = nullptr; - GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -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, nullptr); - - // Search constructor that takes a pointer as parameter - MonoMethod *m; - 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 = 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; - } - } - } - - CRASH_COND(m == nullptr); - - Dictionary *new_dict = memnew(Dictionary(p_from)); - void *args[1] = { &new_dict }; - - MonoException *exc = nullptr; - GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -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(), nullptr); - - if (domain) { - // Workaround to avoid this exception: - // System.Configuration.ConfigurationErrorsException: Error Initializing the configuration system. - // ---> System.ArgumentException: The 'ExeConfigFilename' argument cannot be null. - mono_domain_set_config(domain, ".", ""); - } - - return domain; -} - -String get_type_desc(MonoType *p_type) { - return mono_type_full_name(p_type); -} - -String get_type_desc(MonoReflectionType *p_reftype) { - return get_type_desc(mono_reflection_type_get_type(p_reftype)); -} - -String get_exception_name_and_message(MonoException *p_exc) { - String res; - - MonoClass *klass = mono_object_get_class((MonoObject *)p_exc); - MonoType *type = mono_class_get_type(klass); - - char *full_name = mono_type_full_name(type); - res += full_name; - mono_free(full_name); - - res += ": "; - - MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); - MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, nullptr, nullptr); - res += GDMonoMarshal::mono_string_to_godot(msg); - - return res; -} - -void debug_print_unhandled_exception(MonoException *p_exc) { - print_unhandled_exception(p_exc); - debug_send_unhandled_exception_error(p_exc); -} - -void debug_send_unhandled_exception_error(MonoException *p_exc) { -#ifdef DEBUG_ENABLED - if (!EngineDebugger::is_active()) { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT(GDMonoUtils::get_exception_name_and_message(p_exc)); - } -#endif - return; - } - - 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(); - separator.func = "--- " + RTR("End of inner exception stack trace") + " ---"; - separator.line = 0; - - Vector<ScriptLanguage::StackInfo> si; - String exc_msg; - - 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 = nullptr; - CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc); - - if (unexpected_exc) { - GDMonoInternals::unhandled_exception(unexpected_exc); - return; - } - - Vector<ScriptLanguage::StackInfo> _si; - if (stack_trace != nullptr) { - _si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace); - for (int i = _si.size() - 1; i >= 0; i--) { - si.insert(0, _si[i]); - } - } - - exc_msg += (exc_msg.length() > 0 ? " ---> " : "") + GDMonoUtils::get_exception_name_and_message(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 == nullptr); - - MonoObject *inner_exc = inner_exc_prop->get_value((MonoObject *)p_exc); - if (inner_exc != nullptr) { - si.insert(0, separator); - } - - p_exc = (MonoException *)inner_exc; - } - - String file = si.size() ? si[0].file : __FILE__; - String func = si.size() ? si[0].func : FUNCTION_STR; - int line = si.size() ? si[0].line : __LINE__; - String error_msg = "Unhandled exception"; - - EngineDebugger::get_script_debugger()->send_error(func, file, line, error_msg, exc_msg, true, ERR_HANDLER_ERROR, si); -#endif -} - -void debug_unhandled_exception(MonoException *p_exc) { - GDMonoInternals::unhandled_exception(p_exc); // prints the exception as well -} - -void print_unhandled_exception(MonoException *p_exc) { - mono_print_unhandled_exception((MonoObject *)p_exc); -} - -void set_pending_exception(MonoException *p_exc) { -#ifdef NO_PENDING_EXCEPTIONS - debug_unhandled_exception(p_exc); -#else - if (get_runtime_invoke_count() == 0) { - debug_unhandled_exception(p_exc); - return; - } - - if (!mono_runtime_set_pending_exception(p_exc, false)) { - ERR_PRINT("Exception thrown from managed code, but it could not be set as pending:"); - GDMonoUtils::debug_print_unhandled_exception(p_exc); - } -#endif -} - -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; - MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return ret; -} - -MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return ret; -} - -void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; -} - -MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return ret; -} - -uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error) { - r_error = false; - switch (mono_type_get_type(p_enum_basetype)) { - case MONO_TYPE_BOOLEAN: - return (bool)GDMonoMarshal::unbox<MonoBoolean>(p_boxed) ? 1 : 0; - case MONO_TYPE_CHAR: - return GDMonoMarshal::unbox<uint16_t>(p_boxed); - case MONO_TYPE_U1: - return GDMonoMarshal::unbox<uint8_t>(p_boxed); - case MONO_TYPE_U2: - return GDMonoMarshal::unbox<uint16_t>(p_boxed); - case MONO_TYPE_U4: - return GDMonoMarshal::unbox<uint32_t>(p_boxed); - case MONO_TYPE_U8: - return GDMonoMarshal::unbox<uint64_t>(p_boxed); - case MONO_TYPE_I1: - return GDMonoMarshal::unbox<int8_t>(p_boxed); - case MONO_TYPE_I2: - return GDMonoMarshal::unbox<int16_t>(p_boxed); - case MONO_TYPE_I4: - return GDMonoMarshal::unbox<int32_t>(p_boxed); - case MONO_TYPE_I8: - return GDMonoMarshal::unbox<int64_t>(p_boxed); - default: - r_error = true; - return 0; - } -} - -void dispose(MonoObject *p_mono_object, MonoException **r_exc) { - CACHED_METHOD_THUNK(GodotObject, Dispose).invoke(p_mono_object, r_exc); -} - -namespace Marshal { - -#ifdef MONO_GLUE_ENABLED -#ifdef TOOLS_ENABLED -#define NO_GLUE_RET(m_ret) \ - { \ - if (!GDMonoCache::cached_data.godot_api_cache_updated) \ - return m_ret; \ - } -#else -#define NO_GLUE_RET(m_ret) \ - {} -#endif -#else -#define NO_GLUE_RET(m_ret) \ - { return m_ret; } -#endif - -bool type_is_generic_array(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_dictionary(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_system_generic_list(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsSystemGenericList).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_system_generic_dictionary(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsSystemGenericDictionary).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_ienumerable(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericIEnumerable).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_icollection(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericICollection).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_idictionary(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericIDictionary).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_has_flags_attribute(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeHasFlagsAttribute).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -void get_generic_type_definition(MonoReflectionType *p_reftype, MonoReflectionType **r_generic_reftype) { - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(MarshalUtils, GetGenericTypeDefinition).invoke(p_reftype, r_generic_reftype, &exc); - UNHANDLED_EXCEPTION(exc); -} - -void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) { - 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 = nullptr; - CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes).invoke(p_dict_reftype, r_key_reftype, r_value_reftype, &exc); - UNHANDLED_EXCEPTION(exc); -} - -GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) { - 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(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))); -} -} // namespace Marshal - -ScopeThreadAttach::ScopeThreadAttach() { - if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) { - mono_thread = GDMonoUtils::attach_current_thread(); - } -} - -ScopeThreadAttach::~ScopeThreadAttach() { - if (unlikely(mono_thread)) { - GDMonoUtils::detach_current_thread(mono_thread); - } -} - -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 deleted file mode 100644 index 300cacfa4b..0000000000 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ /dev/null @@ -1,205 +0,0 @@ -/*************************************************************************/ -/* gd_mono_utils.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_UTILS_H -#define GD_MONO_UTILS_H - -#include <mono/metadata/threads.h> - -#include "../mono_gc_handle.h" -#include "../utils/macros.h" -#include "gd_mono_header.h" -#ifdef JAVASCRIPT_ENABLED -#include "gd_mono_wasm_m2n.h" -#endif - -#include "core/object/class_db.h" -#include "core/object/ref_counted.h" - -#define UNHANDLED_EXCEPTION(m_exc) \ - if (unlikely(m_exc != nullptr)) { \ - GDMonoUtils::debug_unhandled_exception(m_exc); \ - GD_UNREACHABLE(); \ - } else \ - ((void)0) - -namespace GDMonoUtils { - -namespace Marshal { - -bool type_is_generic_array(MonoReflectionType *p_reftype); -bool type_is_generic_dictionary(MonoReflectionType *p_reftype); -bool type_is_system_generic_list(MonoReflectionType *p_reftype); -bool type_is_system_generic_dictionary(MonoReflectionType *p_reftype); -bool type_is_generic_ienumerable(MonoReflectionType *p_reftype); -bool type_is_generic_icollection(MonoReflectionType *p_reftype); -bool type_is_generic_idictionary(MonoReflectionType *p_reftype); -bool type_has_flags_attribute(MonoReflectionType *p_reftype); - -void get_generic_type_definition(MonoReflectionType *p_reftype, MonoReflectionType **r_generic_reftype); - -void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype); -void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype); - -GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype); -GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); -} // namespace Marshal - -_FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) { - p_hash ^= p_with_hash + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2); -} - -/** - * 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 nullptr on error - */ -MonoObject *unmanaged_get_managed(Object *unmanaged); - -void set_main_thread(MonoThread *p_thread); -MonoThread *attach_current_thread(); -void detach_current_thread(); -void detach_current_thread(MonoThread *p_mono_thread); -MonoThread *get_current_thread(); -bool is_thread_attached(); - -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); -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); -MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class); - -MonoDomain *create_domain(const String &p_friendly_name); - -String get_type_desc(MonoType *p_type); -String get_type_desc(MonoReflectionType *p_reftype); - -String get_exception_name_and_message(MonoException *p_exc); - -void debug_print_unhandled_exception(MonoException *p_exc); -void debug_send_unhandled_exception_error(MonoException *p_exc); -void debug_unhandled_exception(MonoException *p_exc); -void print_unhandled_exception(MonoException *p_exc); - -/** - * Sets the exception as pending. The exception will be thrown when returning to managed code. - * If no managed method is being invoked by the runtime, the exception will be treated as - * an unhandled exception and the method will not return. - */ -void set_pending_exception(MonoException *p_exc); - -extern thread_local int current_invoke_count; - -_FORCE_INLINE_ int get_runtime_invoke_count() { - return current_invoke_count; -} - -_FORCE_INLINE_ int &get_runtime_invoke_count_ref() { - return current_invoke_count; -} - -MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc); - -MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc); - -void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); -MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); - -uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error); - -void dispose(MonoObject *p_mono_object, MonoException **r_exc); - -struct ScopeThreadAttach { - ScopeThreadAttach(); - ~ScopeThreadAttach(); - -private: - MonoThread *mono_thread = nullptr; -}; - -StringName get_native_godot_class_name(GDMonoClass *p_class); - -template <typename... P> -void add_internal_call(const char *p_name, void (*p_func)(P...)) { -#ifdef JAVASCRIPT_ENABLED - GDMonoWasmM2n::ICallTrampolines<P...>::add(); -#endif - mono_add_internal_call(p_name, (void *)p_func); -} - -template <typename R, typename... P> -void add_internal_call(const char *p_name, R (*p_func)(P...)) { -#ifdef JAVASCRIPT_ENABLED - GDMonoWasmM2n::ICallTrampolinesR<R, P...>::add(); -#endif - mono_add_internal_call(p_name, (void *)p_func); -} -} // namespace GDMonoUtils - -#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(); \ - _runtime_invoke_count_ref += 1; \ - ((void)0) - -#define GD_MONO_END_RUNTIME_INVOKE \ - _runtime_invoke_count_ref -= 1; \ - ((void)0) - -#define GD_MONO_SCOPE_THREAD_ATTACH \ - GDMonoUtils::ScopeThreadAttach __gdmono__scope__thread__attach__; \ - (void)__gdmono__scope__thread__attach__; \ - ((void)0) - -#ifdef DEBUG_ENABLED -#define GD_MONO_ASSERT_THREAD_ATTACHED \ - CRASH_COND(!GDMonoUtils::is_thread_attached()); \ - ((void)0) -#else -#define GD_MONO_ASSERT_THREAD_ATTACHED ((void)0) -#endif - -#endif // GD_MONO_UTILS_H diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp b/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp deleted file mode 100644 index dbfca2dc0c..0000000000 --- a/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************/ -/* gd_mono_wasm_m2n.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 "gd_mono_wasm_m2n.h" - -#ifdef JAVASCRIPT_ENABLED - -#include "core/templates/oa_hash_map.h" - -typedef mono_bool (*GodotMonoM2nIcallTrampolineDispatch)(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs); - -// This extern function is implemented in our patched version of Mono -MONO_API void godot_mono_register_m2n_icall_trampoline_dispatch_hook(GodotMonoM2nIcallTrampolineDispatch hook); - -namespace GDMonoWasmM2n { - -struct HashMapCookieComparator { - static bool compare(const char *p_lhs, const char *p_rhs) { - return strcmp(p_lhs, p_rhs) == 0; - } -}; - -// The default hasher supports 'const char *' C Strings, but we need a custom comparator -OAHashMap<const char *, TrampolineFunc, HashMapHasherDefault, HashMapCookieComparator> trampolines; - -void set_trampoline(const char *cookies, GDMonoWasmM2n::TrampolineFunc trampoline_func) { - trampolines.set(cookies, trampoline_func); -} - -mono_bool trampoline_dispatch_hook(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs) { - TrampolineFunc *trampoline_func = trampolines.lookup_ptr(cookie); - - if (!trampoline_func) { - return false; - } - - (*trampoline_func)(target_func, margs); - return true; -} - -bool initialized = false; - -void lazy_initialize() { - // Doesn't need to be thread safe - if (!initialized) { - initialized = true; - godot_mono_register_m2n_icall_trampoline_dispatch_hook(&trampoline_dispatch_hook); - } -} -} // namespace GDMonoWasmM2n - -#endif diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.h b/modules/mono/mono_gd/gd_mono_wasm_m2n.h deleted file mode 100644 index 83e2750e5a..0000000000 --- a/modules/mono/mono_gd/gd_mono_wasm_m2n.h +++ /dev/null @@ -1,263 +0,0 @@ -/*************************************************************************/ -/* gd_mono_wasm_m2n.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GD_MONO_WASM_M2N_H -#define GD_MONO_WASM_M2N_H - -#ifdef JAVASCRIPT_ENABLED - -#include "core/string/ustring.h" -#include "core/typedefs.h" - -#include <mono/metadata/loader.h> -#include <mono/utils/mono-publib.h> -#include <stdexcept> -#include <type_traits> - -extern "C" { - -struct Mono_InterpMethodArguments { - size_t ilen; - void **iargs; - size_t flen; - double *fargs = nullptr; - void **retval; - size_t is_float_ret; - //#ifdef TARGET_WASM - void *sig = nullptr; - //#endif -}; -} // extern "C" - -namespace GDMonoWasmM2n { - -template <typename T, size_t Size> -struct array { - T elems[Size]; -}; - -template <typename T> -constexpr char get_m2n_cookie_impl() { -#define M2N_REG_COOKIE(m_type, m_cookie) \ - if constexpr (std::is_same_v<m_type, T>) { \ - return m_cookie; \ - } - - M2N_REG_COOKIE(MonoBoolean, 'I'); - M2N_REG_COOKIE(int8_t, 'I'); - M2N_REG_COOKIE(uint8_t, 'I'); - M2N_REG_COOKIE(int16_t, 'I'); - M2N_REG_COOKIE(uint16_t, 'I'); - M2N_REG_COOKIE(int32_t, 'I'); - M2N_REG_COOKIE(uint32_t, 'I'); - M2N_REG_COOKIE(int64_t, 'L'); - M2N_REG_COOKIE(uint64_t, 'L'); - M2N_REG_COOKIE(float, 'F'); - M2N_REG_COOKIE(double, 'D'); - - if constexpr (std::is_pointer_v<T>) { - if constexpr (sizeof(void *) == 4) { - return 'I'; - } else { - return 'L'; - } - } - - if constexpr (std::is_void_v<T>) { - return 'V'; - } - - return 'X'; - -#undef M2N_REG_COOKIE -} - -template <typename T> -constexpr char get_m2n_cookie() { - constexpr char cookie = get_m2n_cookie_impl<T>(); - static_assert(cookie != 'X', "Type not supported in internal call signature."); - return cookie; -} - -template <typename... T> -constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies() { - return array<const char, sizeof...(T) + 2>{ 'V', get_m2n_cookie<T>()..., '\0' }; -} - -template <typename R, typename... T> -constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies_r() { - return array<const char, sizeof...(T) + 2>{ get_m2n_cookie<R>(), get_m2n_cookie<T>()..., '\0' }; -} - -template <typename T> -constexpr size_t calc_m2n_index(size_t &r_int_idx, size_t &r_float_idx) { - constexpr char cookie = get_m2n_cookie<T>(); - - static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D'); - - if constexpr (cookie == 'I' || cookie == 'L') { - size_t ret = r_int_idx; - r_int_idx += cookie == 'I' ? 1 : 2; - return ret; - } else { - size_t ret = r_float_idx; - r_float_idx += cookie == 'F' ? 1 : 2; - return ret; - } -} - -template <typename... P> -constexpr array<size_t, sizeof...(P)> get_indices_for_type() { - size_t int_idx = 0; - size_t float_idx = 0; - return array<size_t, sizeof...(P)>{ calc_m2n_index<P>(int_idx, float_idx)... }; -} - -constexpr size_t fidx(size_t p_x) { - if constexpr (sizeof(void *) == 4) { - return p_x * 2; - } else { - return p_x; - } -} - -template <typename T> -T m2n_arg_cast(Mono_InterpMethodArguments *p_margs, size_t p_idx) { - constexpr char cookie = get_m2n_cookie<T>(); - - static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D'); - - if constexpr (cookie == 'I') { - return (T)(size_t)p_margs->iargs[p_idx]; - } else if constexpr (cookie == 'L') { - static_assert(std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t> || - (sizeof(void *) == 8 && std::is_pointer_v<T>), - "Invalid type for cookie 'L'."); - - union { - T l; - struct { - int32_t lo; - int32_t hi; - } pair; - } p; - - p.pair.lo = (int32_t)(size_t)p_margs->iargs[p_idx]; - p.pair.hi = (int32_t)(size_t)p_margs->iargs[p_idx + 1]; - - return p.l; - } else if constexpr (cookie == 'F') { - return *reinterpret_cast<float *>(&p_margs->fargs[fidx(p_idx)]); - } else if constexpr (cookie == 'D') { - return (T)p_margs->fargs[p_idx]; - } -} - -template <typename... P, size_t... Is> -void m2n_trampoline_with_idx_seq(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) { - constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>(); - typedef void (*Func)(P...); - Func func = (Func)p_target_func; - func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...); -} - -template <typename R, typename... P, size_t... Is> -void m2n_trampoline_with_idx_seq_r(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) { - constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>(); - typedef R (*Func)(P...); - Func func = (Func)p_target_func; - R res = func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...); - *reinterpret_cast<R *>(p_margs->retval) = res; -} - -inline void m2n_trampoline_with_idx_seq_0(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - typedef void (*Func)(); - Func func = (Func)p_target_func; - func(); -} - -template <typename R> -void m2n_trampoline_with_idx_seq_r0(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - typedef R (*Func)(); - Func func = (Func)p_target_func; - R res = func(); - *reinterpret_cast<R *>(p_margs->retval) = res; -} - -template <typename... P> -void m2n_trampoline(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - if constexpr (sizeof...(P) == 0) { - m2n_trampoline_with_idx_seq_0(p_target_func, p_margs); - } else { - m2n_trampoline_with_idx_seq<P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{}); - } -} - -template <typename R, typename... P> -void m2n_trampoline_r(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - if constexpr (sizeof...(P) == 0) { - m2n_trampoline_with_idx_seq_r0<R>(p_target_func, p_margs); - } else { - m2n_trampoline_with_idx_seq_r<R, P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{}); - } -} - -typedef void (*TrampolineFunc)(void *p_target_func, Mono_InterpMethodArguments *p_margs); - -void set_trampoline(const char *cookies, TrampolineFunc trampoline_func); - -void lazy_initialize(); - -template <typename... P> -struct ICallTrampolines { - static constexpr auto cookies = get_m2n_cookies<P...>(); - - static void add() { - lazy_initialize(); - set_trampoline(cookies.elems, &m2n_trampoline<P...>); - } -}; - -template <typename R, typename... P> -struct ICallTrampolinesR { - static constexpr auto cookies = get_m2n_cookies_r<R, P...>(); - - static void add() { - lazy_initialize(); - set_trampoline(cookies.elems, &m2n_trampoline_r<R, P...>); - } -}; - -void initialize(); -} // namespace GDMonoWasmM2n - -#endif - -#endif // GD_MONO_WASM_M2N_H diff --git a/modules/mono/mono_gd/i_mono_class_member.h b/modules/mono/mono_gd/i_mono_class_member.h deleted file mode 100644 index 14e8ca82b9..0000000000 --- a/modules/mono/mono_gd/i_mono_class_member.h +++ /dev/null @@ -1,70 +0,0 @@ -/*************************************************************************/ -/* i_mono_class_member.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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 I_MONO_CLASS_MEMBER_H -#define I_MONO_CLASS_MEMBER_H - -#include "gd_mono_header.h" - -#include <mono/metadata/object.h> - -class IMonoClassMember { -public: - enum Visibility { - PRIVATE, - PROTECTED_AND_INTERNAL, // FAM_AND_ASSEM - INTERNAL, // ASSEMBLY - PROTECTED, // FAMILY - PUBLIC - }; - - enum MemberType { - MEMBER_TYPE_FIELD, - MEMBER_TYPE_PROPERTY, - MEMBER_TYPE_METHOD - }; - - virtual ~IMonoClassMember() {} - - virtual GDMonoClass *get_enclosing_class() const = 0; - - virtual MemberType get_member_type() const = 0; - - virtual StringName get_name() const = 0; - - virtual bool is_static() = 0; - - virtual Visibility get_visibility() = 0; - - virtual bool has_attribute(GDMonoClass *p_attr_class) = 0; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) = 0; -}; - -#endif // I_MONO_CLASS_MEMBER_H diff --git a/modules/mono/mono_gd/managed_type.cpp b/modules/mono/mono_gd/managed_type.cpp deleted file mode 100644 index 5860d7db1e..0000000000 --- a/modules/mono/mono_gd/managed_type.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************/ -/* managed_type.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 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_type.h" - -#include "gd_mono.h" -#include "gd_mono_class.h" - -ManagedType ManagedType::from_class(GDMonoClass *p_class) { - return ManagedType(mono_type_get_type(p_class->get_mono_type()), p_class); -} - -ManagedType ManagedType::from_class(MonoClass *p_mono_class) { - GDMonoClass *tclass = GDMono::get_singleton()->get_class(p_mono_class); - ERR_FAIL_COND_V(!tclass, ManagedType()); - - return ManagedType(mono_type_get_type(tclass->get_mono_type()), tclass); -} - -ManagedType ManagedType::from_type(MonoType *p_mono_type) { - MonoClass *mono_class = mono_class_from_mono_type(p_mono_type); - GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_class); - ERR_FAIL_COND_V(!tclass, ManagedType()); - - return ManagedType(mono_type_get_type(p_mono_type), tclass); -} - -ManagedType ManagedType::from_reftype(MonoReflectionType *p_mono_reftype) { - MonoType *mono_type = mono_reflection_type_get_type(p_mono_reftype); - return from_type(mono_type); -} diff --git a/modules/mono/mono_gd/managed_type.h b/modules/mono/mono_gd/managed_type.h deleted file mode 100644 index 603ff3aca1..0000000000 --- a/modules/mono/mono_gd/managed_type.h +++ /dev/null @@ -1,55 +0,0 @@ -/*************************************************************************/ -/* managed_type.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MANAGED_TYPE_H -#define MANAGED_TYPE_H - -#include <mono/metadata/object.h> - -#include "gd_mono_header.h" - -struct ManagedType { - int type_encoding = 0; - GDMonoClass *type_class = nullptr; - - static ManagedType from_class(GDMonoClass *p_class); - static ManagedType from_class(MonoClass *p_mono_class); - static ManagedType from_type(MonoType *p_mono_type); - static ManagedType from_reftype(MonoReflectionType *p_mono_reftype); - - ManagedType() {} - - ManagedType(int p_type_encoding, GDMonoClass *p_type_class) : - type_encoding(p_type_encoding), - type_class(p_type_class) { - } -}; - -#endif // MANAGED_TYPE_H diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp index 4797d5dae1..7fb983cd37 100644 --- a/modules/mono/mono_gd/support/android_support.cpp +++ b/modules/mono/mono_gd/support/android_support.cpp @@ -359,7 +359,7 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded)); jsize encodedLength = env->GetArrayLength(encoded); - MonoArray *encoded_ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), encodedLength); + MonoArray *encoded_ret = mono_array_new(mono_domain_get(), mono_get_byte_class(), encodedLength); uint8_t *dest = (uint8_t *)mono_array_addr(encoded_ret, uint8_t, 0); env->GetByteArrayRegion(encoded, 0, encodedLength, reinterpret_cast<jbyte *>(dest)); diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 437c4ca54a..55d2138674 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -32,16 +32,14 @@ #include "csharp_script.h" #include "mono_gd/gd_mono_cache.h" -#include "mono_gd/gd_mono_class.h" -#include "mono_gd/gd_mono_marshal.h" -#include "mono_gd/gd_mono_utils.h" -Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter) { +Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, GCHandleIntPtr p_awaiter_handle_ptr) { ERR_FAIL_NULL_V(p_source, ERR_INVALID_DATA); ERR_FAIL_NULL_V(p_target, ERR_INVALID_DATA); // TODO: Use pooling for ManagedCallable instances. - SignalAwaiterCallable *awaiter_callable = memnew(SignalAwaiterCallable(p_target, p_awaiter, p_signal)); + MonoGCHandleData awaiter_handle(p_awaiter_handle_ptr, gdmono::GCHandleType::STRONG_HANDLE); + SignalAwaiterCallable *awaiter_callable = memnew(SignalAwaiterCallable(p_target, awaiter_handle, p_signal)); Callable callable = Callable(awaiter_callable); return p_source->connect(p_signal, callable, Object::CONNECT_ONESHOT); @@ -51,7 +49,7 @@ bool SignalAwaiterCallable::compare_equal(const CallableCustom *p_a, const Calla // Only called if both instances are of type SignalAwaiterCallable. Static cast is safe. const SignalAwaiterCallable *a = static_cast<const SignalAwaiterCallable *>(p_a); const SignalAwaiterCallable *b = static_cast<const SignalAwaiterCallable *>(p_b); - return a->awaiter_handle.handle == b->awaiter_handle.handle; + return a->awaiter_handle.handle.value == b->awaiter_handle.handle.value; } bool SignalAwaiterCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -92,6 +90,10 @@ ObjectID SignalAwaiterCallable::get_object() const { return target_id; } +StringName SignalAwaiterCallable::get_signal() const { + return signal; +} + 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(); @@ -101,38 +103,20 @@ void SignalAwaiterCallable::call(const Variant **p_arguments, int p_argcount, Va "Resumed after await, but class instance is gone."); #endif - MonoArray *signal_args = nullptr; - - if (p_argcount > 0) { - signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_argcount); - - 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); - } - } + bool awaiter_is_null = false; + GDMonoCache::managed_callbacks.SignalAwaiter_SignalCallback(awaiter_handle.get_intptr(), p_arguments, p_argcount, &awaiter_is_null); - MonoObject *awaiter = awaiter_handle.get_target(); - - if (!awaiter) { + if (awaiter_is_null) { r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return; } - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(awaiter, signal_args, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL(); - } else { - r_call_error.error = Callable::CallError::CALL_OK; - } + r_call_error.error = Callable::CallError::CALL_OK; } -SignalAwaiterCallable::SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal) : +SignalAwaiterCallable::SignalAwaiterCallable(Object *p_target, MonoGCHandleData p_awaiter_handle, const StringName &p_signal) : target_id(p_target->get_instance_id()), - awaiter_handle(MonoGCHandleData::new_strong_handle(p_awaiter)), + awaiter_handle(p_awaiter_handle), signal(p_signal) { } @@ -148,7 +132,7 @@ bool EventSignalCallable::compare_equal(const CallableCustom *p_a, const Callabl return false; } - if (a->event_signal != b->event_signal) { + if (a->event_signal_name != b->event_signal_name) { return false; } @@ -163,7 +147,7 @@ bool EventSignalCallable::compare_less(const CallableCustom *p_a, const Callable } uint32_t EventSignalCallable::hash() const { - uint32_t hash = event_signal->field->get_name().hash(); + uint32_t hash = event_signal_name.hash(); return hash_murmur3_one_64(owner->get_instance_id(), hash); } @@ -173,8 +157,7 @@ String EventSignalCallable::get_as_text() const { 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); + return class_name + "::EventSignalMiddleman::" + String(event_signal_name); } CallableCustom::CompareEqualFunc EventSignalCallable::get_compare_equal_func() const { @@ -190,39 +173,32 @@ ObjectID EventSignalCallable::get_object() const { } StringName EventSignalCallable::get_signal() const { - return event_signal->field->get_name(); + return event_signal_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); + GCHandleIntPtr owner_gchandle_intptr = csharp_instance->get_gchandle_intptr(); + + bool awaiter_is_null = false; + GDMonoCache::managed_callbacks.ScriptManagerBridge_RaiseEventSignal( + owner_gchandle_intptr, &event_signal_name, + p_arguments, p_argcount, &awaiter_is_null); - MonoObject *delegate_field_value = event_signal->field->get_value(owner_managed); - if (!delegate_field_value) { - r_call_error.error = Callable::CallError::CALL_OK; + if (awaiter_is_null) { + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; 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; - } + r_call_error.error = Callable::CallError::CALL_OK; } -EventSignalCallable::EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal) : +EventSignalCallable::EventSignalCallable(Object *p_owner, const StringName &p_event_signal_name) : owner(p_owner), - event_signal(p_event_signal) { + event_signal_name(p_event_signal_name) { } diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h index 532aa3e327..a53ae56bf5 100644 --- a/modules/mono/signal_awaiter_utils.h +++ b/modules/mono/signal_awaiter_utils.h @@ -36,9 +36,14 @@ #include "csharp_script.h" #include "mono_gc_handle.h" -Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter); +Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, GCHandleIntPtr p_awaiter_handle_ptr); -class SignalAwaiterCallable : public CallableCustom { +class BaseSignalCallable : public CallableCustom { +public: + virtual StringName get_signal() const = 0; +}; + +class SignalAwaiterCallable : public BaseSignalCallable { ObjectID target_id; MonoGCHandleData awaiter_handle; StringName signal; @@ -59,17 +64,17 @@ public: ObjectID get_object() const override; - _FORCE_INLINE_ StringName get_signal() const { return signal; } + StringName get_signal() const override; 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(Object *p_target, MonoGCHandleData p_awaiter_handle, const StringName &p_signal); ~SignalAwaiterCallable(); }; -class EventSignalCallable : public CallableCustom { +class EventSignalCallable : public BaseSignalCallable { Object *owner = nullptr; - const CSharpScript::EventSignal *event_signal; + StringName event_signal_name; public: static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); @@ -87,11 +92,11 @@ public: ObjectID get_object() const override; - StringName get_signal() const; + StringName get_signal() const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; - EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal); + EventSignalCallable(Object *p_owner, const StringName &p_event_signal_name); }; #endif // SIGNAL_AWAITER_UTILS_H diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp deleted file mode 100644 index 8e37e6943c..0000000000 --- a/modules/mono/utils/mono_reg_utils.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/*************************************************************************/ -/* mono_reg_utils.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "mono_reg_utils.h" -#include "core/io/dir_access.h" - -#ifdef WINDOWS_ENABLED - -#include "core/os/os.h" - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -namespace MonoRegUtils { - -template <int> -REGSAM bitness_sam_impl(); - -template <> -REGSAM bitness_sam_impl<4>() { - return KEY_WOW64_64KEY; -} - -template <> -REGSAM bitness_sam_impl<8>() { - return KEY_WOW64_32KEY; -} - -REGSAM _get_bitness_sam() { - return bitness_sam_impl<sizeof(size_t)>(); -} - -LONG _RegOpenKey(HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult) { - LONG res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, phkResult); - - if (res != ERROR_SUCCESS) { - res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ | _get_bitness_sam(), phkResult); - } - - return res; -} - -LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) { - Vector<WCHAR> buffer; - buffer.resize(512); - DWORD dwBufferSize = buffer.size(); - - LONG res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); - - if (res == ERROR_MORE_DATA) { - // dwBufferSize now contains the actual size - buffer.resize(dwBufferSize); - res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); - } - - if (res == ERROR_SUCCESS) { - r_value = String(buffer.ptr(), buffer.size()); - } else { - r_value = String(); - } - - return res; -} - -LONG _find_mono_in_reg(const String &p_subkey, MonoRegInfo &r_info, bool p_old_reg = false) { - HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - if (!p_old_reg) { - res = _RegKeyQueryString(hKey, "Version", r_info.version); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - } - - res = _RegKeyQueryString(hKey, "SdkInstallRoot", r_info.install_root_dir); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "FrameworkAssemblyDirectory", r_info.assembly_dir); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "MonoConfigDir", r_info.config_dir); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - if (r_info.install_root_dir.ends_with("\\")) { - r_info.bin_dir = r_info.install_root_dir + "bin"; - } else { - r_info.bin_dir = r_info.install_root_dir + "\\bin"; - } - -cleanup: - RegCloseKey(hKey); - return res; -} - -LONG _find_mono_in_reg_old(const String &p_subkey, MonoRegInfo &r_info) { - String default_clr; - - HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "DefaultCLR", default_clr); - - if (res == ERROR_SUCCESS && default_clr.length()) { - r_info.version = default_clr; - res = _find_mono_in_reg(p_subkey + "\\" + default_clr, r_info, true); - } - -cleanup: - RegCloseKey(hKey); - return res; -} - -MonoRegInfo find_mono() { - MonoRegInfo info; - - if (_find_mono_in_reg("Software\\Mono", info) == ERROR_SUCCESS) { - return info; - } - - if (_find_mono_in_reg_old("Software\\Novell\\Mono", info) == ERROR_SUCCESS) { - return info; - } - - return MonoRegInfo(); -} - -String find_msbuild_tools_path() { - String msbuild_tools_path; - - // Try to find 15.0 with vswhere - - String vswhere_path = OS::get_singleton()->get_environment(sizeof(size_t) == 8 ? "ProgramFiles(x86)" : "ProgramFiles"); - vswhere_path += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; - - List<String> vswhere_args; - vswhere_args.push_back("-latest"); - vswhere_args.push_back("-products"); - vswhere_args.push_back("*"); - vswhere_args.push_back("-requires"); - vswhere_args.push_back("Microsoft.Component.MSBuild"); - - String output; - int exit_code; - OS::get_singleton()->execute(vswhere_path, vswhere_args, &output, &exit_code); - - if (exit_code == 0) { - Vector<String> lines = output.split("\n"); - - for (int i = 0; i < lines.size(); i++) { - const String &line = lines[i]; - int sep_idx = line.find(":"); - - if (sep_idx > 0) { - String key = line.substr(0, sep_idx); // No need to trim - - if (key == "installationPath") { - String val = line.substr(sep_idx + 1, line.length()).strip_edges(); - - ERR_BREAK(val.is_empty()); - - if (!val.ends_with("\\")) { - val += "\\"; - } - - // Since VS2019, the directory is simply named "Current" - String msbuild_dir = val + "MSBuild\\Current\\Bin"; - if (DirAccess::exists(msbuild_dir)) { - return msbuild_dir; - } - - // Directory name "15.0" is used in VS 2017 - return val + "MSBuild\\15.0\\Bin"; - } - } - } - } - - // Try to find 14.0 in the Registry - - HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\14.0", &hKey); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "MSBuildToolsPath", msbuild_tools_path); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - -cleanup: - RegCloseKey(hKey); - - return msbuild_tools_path; -} -} // namespace MonoRegUtils - -#endif // WINDOWS_ENABLED diff --git a/modules/mono/utils/mono_reg_utils.h b/modules/mono/utils/mono_reg_utils.h deleted file mode 100644 index 5be60d4930..0000000000 --- a/modules/mono/utils/mono_reg_utils.h +++ /dev/null @@ -1,54 +0,0 @@ -/*************************************************************************/ -/* mono_reg_utils.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MONO_REG_UTILS_H -#define MONO_REG_UTILS_H - -#ifdef WINDOWS_ENABLED - -#include "core/string/ustring.h" - -struct MonoRegInfo { - String version; - String install_root_dir; - String assembly_dir; - String config_dir; - String bin_dir; -}; - -namespace MonoRegUtils { - -MonoRegInfo find_mono(); -String find_msbuild_tools_path(); -} // namespace MonoRegUtils - -#endif // WINDOWS_ENABLED - -#endif // MONO_REG_UTILS_H diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index a1905dfcfe..19ad59a1bc 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -51,6 +51,37 @@ namespace path { +String find_executable(const String &p_name) { +#ifdef WINDOWS_ENABLED + Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false); +#endif + Vector<String> env_path = OS::get_singleton()->get_environment("PATH").split(ENV_PATH_SEP, false); + + if (env_path.is_empty()) { + return String(); + } + + for (int i = 0; i < env_path.size(); i++) { + String p = path::join(env_path[i], p_name); + +#ifdef WINDOWS_ENABLED + for (int j = 0; j < exts.size(); j++) { + String p2 = p + exts[j].to_lower(); // lowercase to reduce risk of case mismatch warning + + if (FileAccess::exists(p2)) { + return p2; + } + } +#else + if (FileAccess::exists(p)) { + return p; + } +#endif + } + + return String(); +} + String cwd() { #ifdef WINDOWS_ENABLED const DWORD expected_size = ::GetCurrentDirectoryW(0, nullptr); diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h index 9a2c757361..d1c3d3ccfd 100644 --- a/modules/mono/utils/path_utils.h +++ b/modules/mono/utils/path_utils.h @@ -36,6 +36,8 @@ namespace path { +String find_executable(const String &p_name); + String join(const String &p_a, const String &p_b); String join(const String &p_a, const String &p_b, const String &p_c); String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d); diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 975f2d8332..b0f94310b8 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -65,7 +65,7 @@ int sfind(const String &p_text, int p_from) { break; case 1: { char32_t c = src[read_pos]; - found = src[read_pos] == 's' || (c >= '0' && c <= '4'); + found = src[read_pos] == 's' || (c >= '0' && c <= '5'); break; } default: @@ -86,32 +86,13 @@ int sfind(const String &p_text, int p_from) { } } // namespace -String sformat(const String &p_text, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4, const Variant &p5) { +String sformat(const String &p_text, const String &p1, const String &p2, + const String &p3, const String &p4, const String &p5, const String &p6) { if (p_text.length() < 2) { return p_text; } - Array args; - - if (p1.get_type() != Variant::NIL) { - args.push_back(p1); - - if (p2.get_type() != Variant::NIL) { - args.push_back(p2); - - if (p3.get_type() != Variant::NIL) { - args.push_back(p3); - - if (p4.get_type() != Variant::NIL) { - args.push_back(p4); - - if (p5.get_type() != Variant::NIL) { - args.push_back(p5); - } - } - } - } - } + String args[6] = { p1, p2, p3, p4, p5, p6 }; String new_string; @@ -125,7 +106,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const int req_index = (c == 's' ? findex++ : c - '0'); new_string += p_text.substr(search_from, result - search_from); - new_string += args[req_index].operator String(); + new_string += args[req_index]; search_from = result + 2; } diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h index fa4c5e89f4..b00dd9dde8 100644 --- a/modules/mono/utils/string_utils.h +++ b/modules/mono/utils/string_utils.h @@ -36,7 +36,8 @@ #include <stdarg.h> -String sformat(const String &p_text, const Variant &p1 = Variant(), const Variant &p2 = Variant(), const Variant &p3 = Variant(), const Variant &p4 = Variant(), const Variant &p5 = Variant()); +String sformat(const String &p_text, const String &p1 = String(), const String &p2 = String(), + const String &p3 = String(), const String &p4 = String(), const String &p5 = String(), const String &p6 = String()); #ifdef TOOLS_ENABLED bool is_csharp_keyword(const String &p_name); |