diff options
Diffstat (limited to 'modules/mono')
31 files changed, 1261 insertions, 479 deletions
diff --git a/modules/mono/README.md b/modules/mono/README.md index ebbc6b0f80..366777cfc1 100644 --- a/modules/mono/README.md +++ b/modules/mono/README.md @@ -43,3 +43,13 @@ 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. + +# Double Precision Support (REAL_T_IS_DOUBLE) + +Follow the above instructions but build Godot with the float=64 argument to scons + +When building the NuGet packages, specify `--float=64` - for example: +```sh +./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin \ + --push-nupkgs-local ~/MyLocalNugetSource --float=64 +``` diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index fa3be684bd..6f66ce9efa 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -195,7 +195,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None): return subprocess.call(args, env=msbuild_env) -def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local): +def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, float_size): target_filenames = [ "GodotSharp.dll", "GodotSharp.pdb", @@ -216,6 +216,8 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local): args = ["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"] if push_nupkgs_local: args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + if float_size == "64": + args += ["/p:GodotFloat64=true"] sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") exit_code = run_msbuild( @@ -256,9 +258,9 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local): return 0 -def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local): +def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, float_size): # Godot API - exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local) + exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, float_size) if exit_code != 0: return exit_code @@ -269,6 +271,8 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p ) if push_nupkgs_local: args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + if float_size == "64": + args += ["/p:GodotFloat64=true"] exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args) if exit_code != 0: return exit_code @@ -277,6 +281,8 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p args = ["/restore", "/t:Build", "/p:Configuration=Release"] if push_nupkgs_local: args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + if float_size == "64": + args += ["/p:GodotFloat64=true"] 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: @@ -300,6 +306,7 @@ def main(): 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="") + parser.add_argument("--float", type=str, default="32", choices=["32", "64"], help="Floating-point precision") args = parser.parse_args() @@ -321,6 +328,7 @@ def main(): args.godot_platform, args.dev_debug, args.push_nupkgs_local, + args.float, ) sys.exit(exit_code) diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index c2d5452837..5d63773096 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -27,293 +27,3 @@ def configure(env, env_mono): if env["tools"]: env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) - - app_host_dir = find_dotnet_app_host_dir(env) - - 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) - - # 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. - - # 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") - - env_mono.Prepend(CPPPATH=app_host_dir) - - env.Append(LIBPATH=[app_host_dir]) - - # 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") - - if env["platform"] == "windows": - env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"]) - - if env.msvc: - env.Append(LINKFLAGS="libnethost.lib") - else: - env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"]) - else: - is_apple = env["platform"] in ["macos", "ios"] - # is_macos = is_apple and not is_ios - - # if is_ios and not is_ios_sim: - # env_mono.Append(CPPDEFINES=["IOS_DEVICE"]) - - if is_apple: - env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path]) - else: - env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"]) - - -def find_dotnet_app_host_dir(env): - dotnet_version = "6.0" - - dotnet_root = env["dotnet_root"] - - 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 dotnet_root: - raise RuntimeError("Cannot find .NET Core Sdk") - - print("Found .NET Core Sdk root directory: " + dotnet_root) - - dotnet_cmd = os.path.join(dotnet_root, "dotnet.exe" if os.name == "nt" else "dotnet") - - runtime_identifier = determine_runtime_identifier(env) - - # 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) - - def get_runtime_path(): - return os.path.join( - dotnet_root, - "packs", - "Microsoft.NETCore.App.Host." + runtime_identifier, - app_host_version, - "runtimes", - runtime_identifier, - "native", - ) - - 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() - - -def find_app_host_version(dotnet_cmd, search_version_str): - import subprocess - from distutils.version import LooseVersion - - search_version = LooseVersion(search_version_str) - found_match = False - - try: - env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") - lines = subprocess.check_output([dotnet_cmd, "--list-runtimes"], env=env).splitlines() - - for line_bytes in lines: - line = line_bytes.decode("utf-8") - if not line.startswith("Microsoft.NETCore.App "): - continue - - parts = line.split(" ", 2) - if len(parts) < 3: - continue - - version_str = parts[1] - - version = LooseVersion(version_str) - - if version >= search_version: - search_version = version - found_match = True - if found_match: - return str(search_version) - except (subprocess.CalledProcessError, OSError) as e: - import sys - - print(e, file=sys.stderr) - - return "" - - -def find_dotnet_arch(dotnet_cmd): - import subprocess - - 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") - - parts = line.split(":", 1) - if len(parts) < 2: - continue - - arch_str = parts[0].strip() - if arch_str != "Architecture": - continue - - 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 - - print(e, file=sys.stderr) - - return "" - - -def find_dotnet_sdk(dotnet_cmd, search_version_str): - import subprocess - from distutils.version import LooseVersion - - search_version = LooseVersion(search_version_str) - - try: - env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") - lines = subprocess.check_output([dotnet_cmd, "--list-sdks"], env=env).splitlines() - - for line_bytes in lines: - line = line_bytes.decode("utf-8") - - parts = line.split(" ", 1) - if len(parts) < 2: - continue - - version_str = parts[0] - - version = LooseVersion(version_str) - - if version < search_version: - continue - - path_part = parts[1] - return path_part[1 : path_part.find("]")] - except (subprocess.CalledProcessError, OSError) as e: - import sys - - print(e, file=sys.stderr) - - return "" - - -def find_dotnet_cli_rid(dotnet_cmd): - import subprocess - - 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 - - parts = line.split() - if len(parts) < 2: - continue - - return parts[1] - except (subprocess.CalledProcessError, OSError) as e: - import sys - - print(e, file=sys.stderr) - - return "" - - -ENV_PATH_SEP = ";" if os.name == "nt" else ":" - - -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) - - search_dirs = path_dirs + [os.getcwd()] # cwd is last in the list - - 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 - - # `dotnet --info` may not specify architecture. In such cases, - # we fallback to the first one we find without architecture. - sdk_path_unknown_arch = "" - - for dir in search_dirs: - path = os.path.join(dir, "dotnet") - - if is_windows: - for extension in windows_exts: - path_with_ext = path + extension - - 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 - elif sdk_arch == "": - sdk_path_unknown_arch = 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 - elif sdk_arch == "": - sdk_path_unknown_arch = path - - return sdk_path_unknown_arch diff --git a/modules/mono/config.py b/modules/mono/config.py index ad78c8c898..15fe79ef8c 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -4,23 +4,13 @@ supported_platforms = ["windows", "macos", "linuxbsd"] def can_build(env, platform): - return not env["arch"].startswith("rv") + if env["arch"].startswith("rv"): + return False + if env["tools"]: + env.module_add_dependencies("mono", ["regex"]) -def get_opts(platform): - from SCons.Variables import BoolVariable, PathVariable - - default_mono_static = platform in ["ios", "web"] - default_mono_bundles_zlib = platform in ["web"] - - return [ - PathVariable( - "dotnet_root", - "Path to the .NET Sdk installation directory for the target platform and architecture", - "", - PathVariable.PathAccept, - ), - ] + return True def configure(env): diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 2e59987fe6..990a95821e 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -784,6 +784,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { for (Object *obj : script->instances) { script->pending_reload_instances.insert(obj->get_instance_id()); + // Since this script instance wasn't a placeholder, add it to the list of placeholders + // that will have to be eventually replaced with a script instance in case it turns into one. + // This list is not cleared after the reload and the collected instances only leave + // the list if the script is instantiated or if it was a tool script but becomes a + // non-tool script in a rebuild. + script->pending_replace_placeholders.insert(obj->get_instance_id()); + RefCounted *rc = Object::cast_to<RefCounted>(obj); if (rc) { rc_instances.push_back(Ref<RefCounted>(rc)); @@ -836,6 +843,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { obj->set_script(Ref<RefCounted>()); // Remove script and existing script instances (placeholder are not removed before domain reload) } + script->was_tool_before_reload = script->tool; script->_clear(); } @@ -924,24 +932,34 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { ScriptInstance *si = obj->get_script_instance(); + // Check if the script must be instantiated or kept as a placeholder + // when the script may not be a tool (see #65266) + bool replace_placeholder = script->pending_replace_placeholders.has(obj->get_instance_id()); + if (!script->is_tool() && script->was_tool_before_reload) { + // The script was a tool before the rebuild so the removal was intentional. + replace_placeholder = false; + script->pending_replace_placeholders.erase(obj->get_instance_id()); + } + #ifdef TOOLS_ENABLED if (si) { // If the script instance is not null, then it must be a placeholder. // Non-placeholder script instances are removed in godot_icall_Object_Disposed. CRASH_COND(!si->is_placeholder()); - if (script->is_tool() || ScriptServer::is_scripting_enabled()) { - // Replace placeholder with a script instance + if (replace_placeholder || script->is_tool() || ScriptServer::is_scripting_enabled()) { + // Replace placeholder with a script instance. CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; - // Backup placeholder script instance state before replacing it with a script instance + // Backup placeholder script instance state before replacing it with a script instance. si->get_property_state(state_backup.properties); ScriptInstance *script_instance = script->instance_create(obj); if (script_instance) { script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); + script->pending_replace_placeholders.erase(obj->get_instance_id()); obj->set_script_instance(script_instance); } } @@ -951,8 +969,24 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #else CRASH_COND(si != nullptr); #endif - // Re-create script instance - obj->set_script(script); // will create the script instance as well + + // Re-create the script instance. + if (replace_placeholder || script->is_tool() || ScriptServer::is_scripting_enabled()) { + // Create script instance or replace placeholder with a script instance. + ScriptInstance *script_instance = script->instance_create(obj); + + if (script_instance) { + script->pending_replace_placeholders.erase(obj->get_instance_id()); + obj->set_script_instance(script_instance); + continue; + } + } + // The script instance could not be instantiated or wasn't in the list of placeholders to replace. + obj->set_script(script); +#if DEBUG_ENABLED + // If we reached here, the instantiated script must be a placeholder. + CRASH_COND(!obj->get_script_instance()->is_placeholder()); +#endif } } @@ -1754,20 +1788,16 @@ void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_f } void CSharpInstance::connect_event_signals() { - CSharpScript *top = script.ptr(); - while (top != nullptr) { - for (CSharpScript::EventSignalInfo &signal : top->get_script_event_signals()) { - String signal_name = signal.name; + // The script signals list includes the signals declared in base scripts. + for (CSharpScript::EventSignalInfo &signal : script->get_script_event_signals()) { + String signal_name = signal.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, signal_name)); - Callable callable(event_signal_callable); - connected_event_signals.push_back(callable); - owner->connect(signal_name, callable); - } - - top = top->base_script.ptr(); + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(signal_name, callable); } } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 3509a5c87d..f2844a051d 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -90,6 +90,9 @@ class CSharpScript : public Script { HashSet<ObjectID> pending_reload_instances; RBMap<ObjectID, StateBackup> pending_reload_state; + + bool was_tool_before_reload = false; + HashSet<ObjectID> pending_replace_placeholders; #endif String source; 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 59ce1da17b..0459257106 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 @@ -62,7 +62,7 @@ </PropertyGroup> <PropertyGroup> - <GodotRealTIsDouble Condition=" '$(GodotRealTIsDouble)' == '' ">false</GodotRealTIsDouble> + <GodotFloat64 Condition=" '$(GodotFloat64)' == '' ">false</GodotFloat64> </PropertyGroup> <!-- Godot DefineConstants. --> 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 aad4ea4553..bff9760b32 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 @@ -10,9 +10,9 @@ <!-- Define constant to determine whether the real_t type in Godot is double precision or not. By default this is false, like the official Godot builds. If someone is using a custom - Godot build where real_t is double, they can override the GodotRealTIsDouble property. + Godot build where real_t is double, they can override the GodotFloat64 property. --> - <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants> + <DefineConstants Condition=" '$(GodotFloat64)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants> </PropertyGroup> <!-- C# source generators --> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs index 7aaadb27be..98ca534c66 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs @@ -39,6 +39,11 @@ namespace Godot.SourceGenerators for (int i = 0; i < typeArgListSyntax.Arguments.Count; i++) { var typeSyntax = typeArgListSyntax.Arguments[i]; + + // Ignore omitted type arguments, e.g.: List<>, Dictionary<,>, etc + if (typeSyntax is OmittedTypeArgumentSyntax) + continue; + var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol; Debug.Assert(typeSymbol != null); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 5ac4f4a47e..1ee31eb1a9 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -133,7 +133,9 @@ namespace Godot.SourceGenerators .Distinct(new MethodOverloadEqualityComparer()) .ToArray(); - source.Append(" private partial class GodotInternal {\n"); + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + source.Append($" public new class MethodName : {symbol.BaseType.FullQualifiedName()}.MethodName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup @@ -144,7 +146,7 @@ namespace Godot.SourceGenerators foreach (string methodName in distinctMethodNames) { - source.Append(" public static readonly StringName MethodName_"); + source.Append(" public new static readonly StringName "); source.Append(methodName); source.Append(" = \""); source.Append(methodName); @@ -157,8 +159,6 @@ namespace Godot.SourceGenerators 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 ") @@ -179,10 +179,10 @@ namespace Godot.SourceGenerators source.Append(" return methods;\n"); source.Append(" }\n"); - - source.Append("#pragma warning restore CS0109\n"); } + source.Append("#pragma warning restore CS0109\n"); + // Generate InvokeGodotClassMethod if (godotClassMethods.Length > 0) @@ -242,7 +242,7 @@ namespace Godot.SourceGenerators private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) { - source.Append(" methods.Add(new(name: GodotInternal.MethodName_") + source.Append(" methods.Add(new(name: MethodName.") .Append(methodInfo.Name) .Append(", returnVal: "); @@ -350,7 +350,7 @@ namespace Godot.SourceGenerators source.Append(" "); if (!isFirstEntry) source.Append("else "); - source.Append("if (method == GodotInternal.MethodName_"); + source.Append("if (method == MethodName."); source.Append(methodName); source.Append(") {\n return true;\n }\n"); } @@ -362,7 +362,7 @@ namespace Godot.SourceGenerators { string methodName = method.Method.Name; - source.Append(" if (method == GodotInternal.MethodName_"); + source.Append(" if (method == MethodName."); source.Append(methodName); source.Append(" && argCount == "); source.Append(method.ParamTypes.Length); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index fc46d82dff..b331e2e794 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -122,14 +122,16 @@ namespace Godot.SourceGenerators var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); - source.Append(" private partial class GodotInternal {\n"); + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + source.Append($" public new class PropertyName : {symbol.BaseType.FullQualifiedName()}.PropertyName {{\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(" public new static readonly StringName "); source.Append(propertyName); source.Append(" = \""); source.Append(propertyName); @@ -139,7 +141,7 @@ namespace Godot.SourceGenerators foreach (var field in godotClassFields) { string fieldName = field.FieldSymbol.Name; - source.Append(" public static readonly StringName PropName_"); + source.Append(" public new static readonly StringName "); source.Append(fieldName); source.Append(" = \""); source.Append(fieldName); @@ -214,8 +216,6 @@ namespace Godot.SourceGenerators // 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 ") @@ -289,7 +289,7 @@ namespace Godot.SourceGenerators if (!isFirstEntry) source.Append("else "); - source.Append("if (name == GodotInternal.PropName_") + source.Append("if (name == PropertyName.") .Append(propertyMemberName) .Append(") {\n") .Append(" ") @@ -313,7 +313,7 @@ namespace Godot.SourceGenerators if (!isFirstEntry) source.Append("else "); - source.Append("if (name == GodotInternal.PropName_") + source.Append("if (name == PropertyName.") .Append(propertyMemberName) .Append(") {\n") .Append(" value = ") @@ -342,7 +342,7 @@ namespace Godot.SourceGenerators { source.Append(" properties.Add(new(type: (Godot.Variant.Type)") .Append((int)propertyInfo.Type) - .Append(", name: GodotInternal.PropName_") + .Append(", name: PropertyName.") .Append(propertyInfo.Name) .Append(", hint: (Godot.PropertyHint)") .Append((int)propertyInfo.Hint) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index c7745391d0..65dadcb801 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -243,7 +243,7 @@ namespace Godot.SourceGenerators source.Append(" = "); source.Append(exportedMember.Value ?? "default"); source.Append(";\n"); - source.Append(" values.Add(GodotInternal.PropName_"); + source.Append(" values.Add(PropertyName."); source.Append(exportedMember.Name); source.Append(", "); source.Append(defaultValueLocalName); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 39a99ff8ba..a40220565f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -159,7 +159,7 @@ namespace Godot.SourceGenerators { string propertyName = property.PropertySymbol.Name; - source.Append(" info.AddProperty(GodotInternal.PropName_") + source.Append(" info.AddProperty(PropertyName.") .Append(propertyName) .Append(", ") .AppendManagedToVariantExpr(string.Concat("this.", propertyName), property.Type) @@ -172,7 +172,7 @@ namespace Godot.SourceGenerators { string fieldName = field.FieldSymbol.Name; - source.Append(" info.AddProperty(GodotInternal.PropName_") + source.Append(" info.AddProperty(PropertyName.") .Append(fieldName) .Append(", ") .AppendManagedToVariantExpr(string.Concat("this.", fieldName), field.Type) @@ -185,7 +185,7 @@ namespace Godot.SourceGenerators { string signalName = signalDelegate.Name; - source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_") + source.Append(" info.AddSignalEventDelegate(SignalName.") .Append(signalName) .Append(", this.backing_") .Append(signalName) @@ -204,7 +204,7 @@ namespace Godot.SourceGenerators { string propertyName = property.PropertySymbol.Name; - source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + source.Append(" if (info.TryGetProperty(PropertyName.") .Append(propertyName) .Append(", out var _value_") .Append(propertyName) @@ -223,7 +223,7 @@ namespace Godot.SourceGenerators { string fieldName = field.FieldSymbol.Name; - source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + source.Append(" if (info.TryGetProperty(PropertyName.") .Append(fieldName) .Append(", out var _value_") .Append(fieldName) @@ -245,7 +245,7 @@ namespace Godot.SourceGenerators source.Append(" if (info.TryGetSignalEventDelegate<") .Append(signalDelegateQualifiedName) - .Append(">(GodotInternal.SignalName_") + .Append(">(SignalName.") .Append(signalName) .Append(", out var _value_") .Append(signalName) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 6b06f10db1..1df41a905b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -173,14 +173,16 @@ namespace Godot.SourceGenerators godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); } - source.Append(" private partial class GodotInternal {\n"); + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + source.Append($" public new class SignalName : {symbol.BaseType.FullQualifiedName()}.SignalName {{\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(" public new static readonly StringName "); source.Append(signalName); source.Append(" = \""); source.Append(signalName); @@ -193,8 +195,6 @@ namespace Godot.SourceGenerators 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 ") @@ -215,10 +215,10 @@ namespace Godot.SourceGenerators source.Append(" return signals;\n"); source.Append(" }\n"); - - source.Append("#pragma warning restore CS0109\n"); } + source.Append("#pragma warning restore CS0109\n"); + // Generate signal event foreach (var signalDelegate in godotSignalDelegates) @@ -291,7 +291,7 @@ namespace Godot.SourceGenerators private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) { - source.Append(" signals.Add(new(name: GodotInternal.SignalName_") + source.Append(" signals.Add(new(name: SignalName.") .Append(methodInfo.Name) .Append(", returnVal: "); @@ -400,7 +400,7 @@ namespace Godot.SourceGenerators string signalName = signal.Name; var invokeMethodData = signal.InvokeMethodData; - source.Append(" if (signal == GodotInternal.SignalName_"); + source.Append(" if (signal == SignalName."); source.Append(signalName); source.Append(" && argCount == "); source.Append(invokeMethodData.ParamTypes.Length); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 15a40c8ca5..c27bb959fe 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -113,7 +113,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #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 Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" }; 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 diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp new file mode 100644 index 0000000000..bdc8fac8b5 --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -0,0 +1,335 @@ +/*************************************************************************/ +/* hostfxr_resolver.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. */ +/*************************************************************************/ + +/* +Adapted to Godot from the nethost library: https://github.com/dotnet/runtime/tree/main/src/native/corehost +*/ + +/* +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +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 "hostfxr_resolver.h" + +#include "core/config/engine.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/os/os.h" + +#ifdef WINDOWS_ENABLED +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif + +#include "../utils/path_utils.h" +#include "semver.h" + +// We don't use libnethost as it gives us issues with some compilers. +// This file tries to mimic libnethost's hostfxr_resolver search logic. We try to use the +// same function names for easier comparing in case we need to update this in the future. + +namespace { + +String get_hostfxr_file_name() { +#if defined(WINDOWS_ENABLED) || defined(UWP_ENABLED) + return "hostfxr.dll"; +#elif defined(OSX_ENABLED) || defined(IOS_ENABLED) + return "libhostfxr.dylib"; +#else + return "libhostfxr.so"; +#endif +} + +bool get_latest_fxr(const String &fxr_root, String &r_fxr_path) { + godotsharp::SemVerParser sem_ver_parser; + + bool found_ver = false; + godotsharp::SemVer latest_ver; + String latest_ver_str; + + Ref<DirAccess> da = DirAccess::open(fxr_root); + da->list_dir_begin(); + for (String dir = da->get_next(); !dir.is_empty(); dir = da->get_next()) { + if (!da->current_is_dir() || dir == "." || dir == "..") { + continue; + } + + String ver = dir.get_file(); + + godotsharp::SemVer fx_ver; + if (sem_ver_parser.parse(ver, fx_ver)) { + if (!found_ver || fx_ver > latest_ver) { + latest_ver = fx_ver; + latest_ver_str = ver; + found_ver = true; + } + } + } + + if (!found_ver) { + return false; + } + + String fxr_with_ver = path::join(fxr_root, latest_ver_str); + String hostfxr_file_path = path::join(fxr_with_ver, get_hostfxr_file_name()); + + ERR_FAIL_COND_V_MSG(!FileAccess::exists(hostfxr_file_path), false, "Missing hostfxr library in directory: " + fxr_with_ver); + + r_fxr_path = hostfxr_file_path; + + return true; +} + +#ifdef WINDOWS_ENABLED +typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL); + +BOOL is_wow64() { + BOOL wow64 = FALSE; + + LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); + + if (fnIsWow64Process) { + if (!fnIsWow64Process(GetCurrentProcess(), &wow64)) { + wow64 = FALSE; + } + } + + return wow64; +} +#endif + +static const char *arch_name_map[][2] = { + { "arm32", "arm" }, + { "arm64", "arm64" }, + { "rv64", "riscv64" }, + { "x86_64", "x64" }, + { "x86_32", "x86" }, + { nullptr, nullptr } +}; + +String get_dotnet_arch() { + String arch = Engine::get_singleton()->get_architecture_name(); + + int idx = 0; + while (arch_name_map[idx][0] != nullptr) { + if (arch_name_map[idx][0] == arch) { + return arch_name_map[idx][1]; + } + idx++; + } + + return ""; +} + +bool get_default_installation_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String program_files_env; + if (is_wow64()) { + // Running x86 on x64, looking for x86 install + program_files_env = "ProgramFiles(x86)"; + } else { + program_files_env = "ProgramFiles"; + } + + String program_files_dir = OS::get_singleton()->get_environment(program_files_env); + + if (program_files_dir.is_empty()) { + return false; + } + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(program_files_dir, "dotnet", "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet.exe"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + r_dotnet_root = path::join(program_files_dir, "dotnet"); + return true; +#elif defined(TARGET_OSX) + r_dotnet_root = "/usr/local/share/dotnet"; + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(r_dotnet_root, "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + return true; +#else + r_dotnet_root = "/usr/share/dotnet"; + return true; +#endif +} + +bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) { + Error err = OK; + Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err); + + if (f.is_null() || err != OK) { + return false; + } + + String line = f->get_line(); + + if (line.is_empty()) { + return false; + } + + r_dotnet_root = line; + return true; +} + +bool get_dotnet_self_registered_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String sub_key = "SOFTWARE\\dotnet\\Setup\\InstalledVersions\\" + get_dotnet_arch(); + Char16String value = String("InstallLocation").utf16(); + + HKEY hkey = NULL; + LSTATUS result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(sub_key.utf16().get_data()), 0, KEY_READ | KEY_WOW64_32KEY, &hkey); + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD size = 0; + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, nullptr, &size); + if (result != ERROR_SUCCESS || size == 0) { + RegCloseKey(hkey); + return false; + } + + Vector<WCHAR> buffer; + buffer.resize(size / sizeof(WCHAR)); + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, (LPBYTE)buffer.ptrw(), &size); + if (result != ERROR_SUCCESS) { + RegCloseKey(hkey); + return false; + } + + r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()); + RegCloseKey(hkey); + return true; +#else + String install_location_file = path::join("/etc/dotnet", "install_location_" + get_dotnet_arch().to_lower()); + if (get_install_location_from_file(install_location_file, r_dotnet_root)) { + return true; + } + + if (FileAccess::exists(install_location_file)) { + // Don't try with the legacy location, this will fall back to the hard-coded default install location + return false; + } + + String legacy_install_location_file = path::join("/etc/dotnet", "install_location"); + return get_install_location_from_file(legacy_install_location_file, r_dotnet_root); +#endif +} + +bool get_file_path_from_env(const String &p_env_key, String &r_dotnet_root) { + String env_value = OS::get_singleton()->get_environment(p_env_key); + + if (!env_value.is_empty()) { + env_value = path::realpath(env_value); + + if (DirAccess::exists(env_value)) { + r_dotnet_root = env_value; + return true; + } + } + + return false; +} + +bool get_dotnet_root_from_env(String &r_dotnet_root) { + String dotnet_root_env = "DOTNET_ROOT"; + String arch_for_env = get_dotnet_arch(); + + if (!arch_for_env.is_empty()) { + // DOTNET_ROOT_<arch> + if (get_file_path_from_env(dotnet_root_env + "_" + arch_for_env.to_upper(), r_dotnet_root)) { + return true; + } + } + +#ifdef WINDOWS_ENABLED + // WoW64-only: DOTNET_ROOT(x86) + if (is_wow64() && get_file_path_from_env("DOTNET_ROOT(x86)", r_dotnet_root)) { + return true; + } +#endif + + // DOTNET_ROOT + return get_file_path_from_env(dotnet_root_env, r_dotnet_root); +} + +} //namespace + +bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_fxr_path) { + String fxr_dir = path::join(p_dotnet_root, "host", "fxr"); + ERR_FAIL_COND_V_MSG(!DirAccess::exists(fxr_dir), false, "The host fxr folder does not exist: " + fxr_dir); + return get_latest_fxr(fxr_dir, r_fxr_path); +} + +bool godotsharp::hostfxr_resolver::try_get_path(String &r_dotnet_root, String &r_fxr_path) { + if (!get_dotnet_root_from_env(r_dotnet_root) && + !get_dotnet_self_registered_dir(r_dotnet_root) && + !get_default_installation_dir(r_dotnet_root)) { + return false; + } + + return try_get_path_from_dotnet_root(r_dotnet_root, r_fxr_path); +} diff --git a/modules/mono/editor/hostfxr_resolver.h b/modules/mono/editor/hostfxr_resolver.h new file mode 100644 index 0000000000..0f029ab7ae --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* hostfxr_resolver.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 HOSTFXR_RESOLVER_H +#define HOSTFXR_RESOLVER_H + +#include "core/string/ustring.h" + +namespace godotsharp { +namespace hostfxr_resolver { + +bool try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_out_fxr_path); +bool try_get_path(String &r_out_dotnet_root, String &r_out_fxr_path); + +} //namespace hostfxr_resolver +} //namespace godotsharp + +#endif // HOSTFXR_RESOLVER_H diff --git a/modules/mono/editor/semver.cpp b/modules/mono/editor/semver.cpp new file mode 100644 index 0000000000..1656d0932f --- /dev/null +++ b/modules/mono/editor/semver.cpp @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* semver.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 "semver.h" + +bool godotsharp::SemVer::parse_digit_only_field(const String &p_field, uint64_t &r_result) { + if (p_field.is_empty()) { + return false; + } + + int64_t integer = 0; + + for (int i = 0; i < p_field.length(); i++) { + char32_t c = p_field[i]; + if (is_digit(c)) { + bool overflow = ((uint64_t)integer > UINT64_MAX / 10) || ((uint64_t)integer == UINT64_MAX / 10 && c > '5'); + ERR_FAIL_COND_V_MSG(overflow, false, "Cannot represent '" + p_field + "' as a 64-bit unsigned integer, since the value is too large."); + integer *= 10; + integer += c - '0'; + } else { + return false; + } + } + + r_result = (uint64_t)integer; + return true; +} + +int godotsharp::SemVer::cmp(const godotsharp::SemVer &p_a, const godotsharp::SemVer &p_b) { + if (p_a.major != p_b.major) { + return p_a.major > p_b.major ? 1 : -1; + } + + if (p_a.minor != p_b.minor) { + return p_a.minor > p_b.minor ? 1 : -1; + } + + if (p_a.patch != p_b.patch) { + return p_a.patch > p_b.patch ? 1 : -1; + } + + if (p_a.prerelease.is_empty() && p_b.prerelease.is_empty()) { + return 0; + } + + if (p_a.prerelease.is_empty() || p_b.prerelease.is_empty()) { + return p_a.prerelease.is_empty() ? 1 : -1; + } + + if (p_a.prerelease != p_b.prerelease) { + // This could be optimized, but I'm too lazy + + Vector<String> a_field_set = p_a.prerelease.split("."); + Vector<String> b_field_set = p_b.prerelease.split("."); + + int a_field_count = a_field_set.size(); + int b_field_count = b_field_set.size(); + + int min_field_count = MIN(a_field_count, b_field_count); + + for (int i = 0; i < min_field_count; i++) { + const String &a_field = a_field_set[i]; + const String &b_field = b_field_set[i]; + + if (a_field == b_field) { + continue; + } + + uint64_t a_num; + bool a_is_digit_only = parse_digit_only_field(a_field, a_num); + + uint64_t b_num; + bool b_is_digit_only = parse_digit_only_field(b_field, b_num); + + if (a_is_digit_only && b_is_digit_only) { + // Identifiers consisting of only digits are compared numerically. + + if (a_num == b_num) { + continue; + } + + return a_num > b_num ? 1 : -1; + } + + if (a_is_digit_only || b_is_digit_only) { + // Numeric identifiers always have lower precedence than non-numeric identifiers. + return b_is_digit_only ? 1 : -1; + } + + // Identifiers with letters or hyphens are compared lexically in ASCII sort order. + return a_field > b_field ? 1 : -1; + } + + if (a_field_count != b_field_count) { + // A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal. + return a_field_count > b_field_count ? 1 : -1; + } + } + + return 0; +} + +bool godotsharp::SemVerParser::parse(const String &p_ver_text, godotsharp::SemVer &r_semver) { + if (!regex.is_valid() && regex.get_pattern().is_empty()) { + regex.compile("^(?P<major>0|[1-9]\\d*)\\.(?P<minor>0|[1-9]\\d*)\\.(?P<patch>0|[1-9]\\d*)(?:-(?P<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + ERR_FAIL_COND_V(!regex.is_valid(), false); + } + + Ref<RegExMatch> match = regex.search(p_ver_text); + + if (match.is_valid()) { + r_semver = SemVer( + match->get_string("major").to_int(), + match->get_string("minor").to_int(), + match->get_string("patch").to_int(), + match->get_string("prerelease"), + match->get_string("buildmetadata")); + return true; + } + + return false; +} diff --git a/modules/mono/editor/semver.h b/modules/mono/editor/semver.h new file mode 100644 index 0000000000..48ea8b043e --- /dev/null +++ b/modules/mono/editor/semver.h @@ -0,0 +1,106 @@ +/*************************************************************************/ +/* semver.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 SEMVER_H +#define SEMVER_H + +#include "core/string/ustring.h" +#include "modules/regex/regex.h" + +// <sys/sysmacros.h> is included somewhere, which defines major(dev) to gnu_dev_major(dev) +#if defined(major) +#undef major +#endif +#if defined(minor) +#undef minor +#endif + +namespace godotsharp { + +struct SemVer { +private: + static bool parse_digit_only_field(const String &p_field, uint64_t &r_result); + + static int cmp(const SemVer &p_a, const SemVer &p_b); + +public: + int major = 0; + int minor = 0; + int patch = 0; + String prerelease; + String build_metadata; + + bool operator==(const SemVer &b) const { + return cmp(*this, b) == 0; + } + + bool operator!=(const SemVer &b) const { + return !operator==(b); + } + + bool operator<(const SemVer &b) const { + return cmp(*this, b) < 0; + } + + bool operator>(const SemVer &b) const { + return cmp(*this, b) > 0; + } + + bool operator<=(const SemVer &b) const { + return cmp(*this, b) <= 0; + } + + bool operator>=(const SemVer &b) const { + return cmp(*this, b) >= 0; + } + + SemVer() {} + + SemVer(int p_major, int p_minor, int p_patch, + const String &p_prerelease, const String &p_build_metadata) : + major(p_major), + minor(p_minor), + patch(p_patch), + prerelease(p_prerelease), + build_metadata(p_build_metadata) { + } +}; + +struct SemVerParser { +private: + RegEx regex; + +public: + bool parse(const String &p_ver_text, SemVer &r_semver); +}; + +} //namespace godotsharp + +#endif // SEMVER_H diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs index 17f680361d..d8a6644a66 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs @@ -133,15 +133,6 @@ namespace Godot } /// <summary> - /// Returns the area of the <see cref="AABB"/>. - /// </summary> - /// <returns>The area.</returns> - public real_t GetArea() - { - return _size.x * _size.y * _size.z; - } - - /// <summary> /// Gets the position of one of the 8 endpoints of the <see cref="AABB"/>. /// </summary> /// <param name="idx">Which endpoint to get.</param> @@ -321,6 +312,15 @@ namespace Godot } /// <summary> + /// Returns the volume of the <see cref="AABB"/>. + /// </summary> + /// <returns>The volume.</returns> + public real_t GetVolume() + { + return _size.x * _size.y * _size.z; + } + + /// <summary> /// Returns a copy of the <see cref="AABB"/> grown a given amount of units towards all the sides. /// </summary> /// <param name="by">The amount to grow by.</param> @@ -340,30 +340,6 @@ namespace Godot } /// <summary> - /// Returns <see langword="true"/> if the <see cref="AABB"/> is flat or empty, - /// or <see langword="false"/> otherwise. - /// </summary> - /// <returns> - /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has area. - /// </returns> - public bool HasNoArea() - { - return _size.x <= 0f || _size.y <= 0f || _size.z <= 0f; - } - - /// <summary> - /// Returns <see langword="true"/> if the <see cref="AABB"/> has no surface (no size), - /// or <see langword="false"/> otherwise. - /// </summary> - /// <returns> - /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has area. - /// </returns> - public bool HasNoSurface() - { - return _size.x <= 0f && _size.y <= 0f && _size.z <= 0f; - } - - /// <summary> /// Returns <see langword="true"/> if the <see cref="AABB"/> contains a point, /// or <see langword="false"/> otherwise. /// </summary> @@ -390,6 +366,34 @@ namespace Godot } /// <summary> + /// Returns <see langword="true"/> if the <see cref="AABB"/> + /// has a surface or a length, and <see langword="false"/> + /// if the <see cref="AABB"/> is empty (all components + /// of <see cref="Size"/> are zero or negative). + /// </summary> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has surface. + /// </returns> + public bool HasSurface() + { + return _size.x > 0.0f || _size.y > 0.0f || _size.z > 0.0f; + } + + /// <summary> + /// Returns <see langword="true"/> if the <see cref="AABB"/> has + /// area, and <see langword="false"/> if the <see cref="AABB"/> + /// is linear, empty, or has a negative <see cref="Size"/>. + /// See also <see cref="GetVolume"/>. + /// </summary> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has volume. + /// </returns> + public bool HasVolume() + { + return _size.x > 0.0f && _size.y > 0.0f && _size.z > 0.0f; + } + + /// <summary> /// Returns the intersection of this <see cref="AABB"/> and <paramref name="with"/>. /// </summary> /// <param name="with">The other <see cref="AABB"/>.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 3884781988..092724a6b1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -130,7 +130,6 @@ namespace Godot.Bridge { // 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) @@ -151,6 +150,8 @@ namespace Godot.Bridge } } + var obj = (Object)FormatterServices.GetUninitializedObject(scriptType); + var parameters = ctor.GetParameters(); int paramCount = parameters.Length; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index b30012d214..f2667c6807 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -634,7 +634,7 @@ namespace Godot /// <param name="outFrom">The start value for the output interpolation.</param> /// <param name="outTo">The destination value for the output interpolation.</param> /// <returns>The resulting mapped value mapped.</returns> - public static real_t RangeLerp(real_t value, real_t inFrom, real_t inTo, real_t outFrom, real_t outTo) + public static real_t Remap(real_t value, real_t inFrom, real_t inTo, real_t outFrom, real_t outTo) { return Lerp(outFrom, outTo, InverseLerp(inFrom, inTo, value)); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 0b475fec19..e80d75dacf 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -234,15 +234,17 @@ namespace Godot } /// <summary> - /// Returns <see langword="true"/> if the <see cref="Rect2"/> is flat or empty, - /// or <see langword="false"/> otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2"/> has + /// area, and <see langword="false"/> if the <see cref="Rect2"/> + /// is linear, empty, or has a negative <see cref="Size"/>. + /// See also <see cref="GetArea"/>. /// </summary> /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> has area. /// </returns> - public bool HasNoArea() + public bool HasArea() { - return _size.x <= 0 || _size.y <= 0; + return _size.x > 0.0f && _size.y > 0.0f; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs index 8a2a98d6ee..b2768476cc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs @@ -236,15 +236,17 @@ namespace Godot } /// <summary> - /// Returns <see langword="true"/> if the <see cref="Rect2i"/> is flat or empty, - /// or <see langword="false"/> otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2i"/> has + /// area, and <see langword="false"/> if the <see cref="Rect2i"/> + /// is linear, empty, or has a negative <see cref="Size"/>. + /// See also <see cref="GetArea"/>. /// </summary> /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="Rect2i"/> has area. /// </returns> - public bool HasNoArea() + public bool HasArea() { - return _size.x <= 0 || _size.y <= 0; + return _size.x > 0 && _size.y > 0; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index d1962c68cf..e2da41ff47 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -187,7 +187,7 @@ namespace Godot ( Mathf.CubicInterpolate(x, b.x, preA.x, postB.x, weight), Mathf.CubicInterpolate(y, b.y, preA.y, postB.y, weight), - Mathf.CubicInterpolate(y, b.z, preA.z, postB.z, weight), + Mathf.CubicInterpolate(z, b.z, preA.z, postB.z, weight), Mathf.CubicInterpolate(w, b.w, preA.w, postB.w, weight) ); } @@ -212,7 +212,7 @@ namespace Godot ( Mathf.CubicInterpolateInTime(x, b.x, preA.x, postB.x, weight, t, preAT, postBT), Mathf.CubicInterpolateInTime(y, b.y, preA.y, postB.y, weight, t, preAT, postBT), - Mathf.CubicInterpolateInTime(y, b.z, preA.z, postB.z, weight, t, preAT, postBT), + Mathf.CubicInterpolateInTime(z, b.z, preA.z, postB.z, weight, t, preAT, postBT), Mathf.CubicInterpolateInTime(w, b.w, preA.w, postB.w, weight, t, preAT, postBT) ); } @@ -258,7 +258,7 @@ namespace Godot /// <returns>The dot product of the two vectors.</returns> public real_t Dot(Vector4 with) { - return (x * with.x) + (y * with.y) + (z * with.z) + (w + with.w); + return (x * with.x) + (y * with.y) + (z * with.z) + (w * with.w); } /// <summary> @@ -455,6 +455,7 @@ namespace Godot /// This can also be used to round to an arbitrary number of decimals. /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> + /// <returns>The snapped vector.</returns> public Vector4 Snapped(Vector4 step) { return new Vector4( @@ -632,6 +633,56 @@ namespace Godot } /// <summary> + /// Gets the remainder of each component of the <see cref="Vector4"/> + /// with the components of the given <see cref="real_t"/>. + /// This operation uses truncated division, which is often not desired + /// as it does not work well with negative numbers. + /// Consider using <see cref="PosMod(real_t)"/> instead + /// if you want to handle negative numbers. + /// </summary> + /// <example> + /// <code> + /// GD.Print(new Vector4(10, -20, 30, 40) % 7); // Prints "(3, -6, 2, 5)" + /// </code> + /// </example> + /// <param name="vec">The dividend vector.</param> + /// <param name="divisor">The divisor value.</param> + /// <returns>The remainder vector.</returns> + public static Vector4 operator %(Vector4 vec, real_t divisor) + { + vec.x %= divisor; + vec.y %= divisor; + vec.z %= divisor; + vec.w %= divisor; + return vec; + } + + /// <summary> + /// Gets the remainder of each component of the <see cref="Vector4"/> + /// with the components of the given <see cref="Vector4"/>. + /// This operation uses truncated division, which is often not desired + /// as it does not work well with negative numbers. + /// Consider using <see cref="PosMod(Vector4)"/> instead + /// if you want to handle negative numbers. + /// </summary> + /// <example> + /// <code> + /// GD.Print(new Vector4(10, -20, 30, 10) % new Vector4(7, 8, 9, 10)); // Prints "(3, -4, 3, 0)" + /// </code> + /// </example> + /// <param name="vec">The dividend vector.</param> + /// <param name="divisorv">The divisor vector.</param> + /// <returns>The remainder vector.</returns> + public static Vector4 operator %(Vector4 vec, Vector4 divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + vec.z %= divisorv.z; + vec.w %= divisorv.w; + return vec; + } + + /// <summary> /// Returns <see langword="true"/> if the vectors 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/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index aae7a5ebfa..5827d3e591 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -36,6 +36,7 @@ </ItemGroup> <PropertyGroup> <DefineConstants>$(DefineConstants);GODOT</DefineConstants> + <DefineConstants Condition=" '$(GodotFloat64)' == 'true' ">REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants> </PropertyGroup> <ItemGroup> <PackageReference Include="ReflectionAnalyzers" Version="0.1.22-dev" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" /> diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index ebf09aab7b..5d69ad8ec6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -25,6 +25,7 @@ </PropertyGroup> <PropertyGroup> <DefineConstants>$(DefineConstants);GODOT</DefineConstants> + <DefineConstants Condition=" '$(GodotFloat64)' == 'true' ">REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\GodotSharp\GodotSharp.csproj"> diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index e698e92d7a..3b87d9248a 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -43,12 +43,13 @@ #include "../utils/path_utils.h" #include "gd_mono_cache.h" +#include "../thirdparty/coreclr_delegates.h" +#include "../thirdparty/hostfxr.h" + #ifdef TOOLS_ENABLED -#include <nethost.h> +#include "../editor/hostfxr_resolver.h" #endif -#include <coreclr_delegates.h> -#include <hostfxr.h> #ifdef UNIX_ENABLED #include <dlfcn.h> #endif @@ -88,85 +89,43 @@ HostFxrCharString str_to_hostfxr(const String &p_str) { #endif } -#ifdef TOOLS_ENABLED -String str_from_hostfxr(const char_t *p_buffer) { -#ifdef _WIN32 - return String::utf16((const char16_t *)p_buffer); -#else - return String::utf8((const char *)p_buffer); -#endif -} -#endif - const char_t *get_data(const HostFxrCharString &p_char_str) { return (const char_t *)p_char_str.get_data(); } -#ifdef TOOLS_ENABLED -String find_hostfxr(size_t p_known_buffer_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_buffer_size); - - int rc = get_hostfxr_path(buffer.ptrw(), &p_known_buffer_size, p_get_hostfxr_params); - - ERR_FAIL_COND_V_MSG(rc != 0, String(), "get_hostfxr_path failed with code: " + itos(rc)); - - return str_from_hostfxr(buffer.ptr()); -} -#endif - String find_hostfxr() { #ifdef TOOLS_ENABLED - 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); + String dotnet_root; + String fxr_path; + if (godotsharp::hostfxr_resolver::try_get_path(dotnet_root, fxr_path)) { + return fxr_path; } - 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); - } + // hostfxr_resolver doesn't look for dotnet in `PATH`. If it fails, we try to find the dotnet + // executable in `PATH` here 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. + + dotnet_root = dotnet_exe.get_base_dir(); + if (godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(dotnet_root, fxr_path)) { + return fxr_path; } } - 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."); - } + 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."); return String(); #else diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 55d2138674..66c9eca616 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -42,7 +42,7 @@ Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signa 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); + return p_source->connect(p_signal, callable, Object::CONNECT_ONE_SHOT); } bool SignalAwaiterCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { diff --git a/modules/mono/thirdparty/coreclr_delegates.h b/modules/mono/thirdparty/coreclr_delegates.h new file mode 100644 index 0000000000..914ab592df --- /dev/null +++ b/modules/mono/thirdparty/coreclr_delegates.h @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __CORECLR_DELEGATES_H__ +#define __CORECLR_DELEGATES_H__ + +#include <stdint.h> + +#if defined(_WIN32) + #define CORECLR_DELEGATE_CALLTYPE __stdcall + #ifdef _WCHAR_T_DEFINED + typedef wchar_t char_t; + #else + typedef unsigned short char_t; + #endif +#else + #define CORECLR_DELEGATE_CALLTYPE + typedef char char_t; +#endif + +#define UNMANAGEDCALLERSONLY_METHOD ((const char_t*)-1) + +// Signature of delegate returned by coreclr_delegate_type::load_assembly_and_get_function_pointer +typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_and_get_function_pointer_fn)( + const char_t *assembly_path /* Fully qualified path to assembly */, + const char_t *type_name /* Assembly qualified type name */, + const char_t *method_name /* Public static method name compatible with delegateType */, + const char_t *delegate_type_name /* Assembly qualified delegate type name or null + or UNMANAGEDCALLERSONLY_METHOD if the method is marked with + the UnmanagedCallersOnlyAttribute. */, + void *reserved /* Extensibility parameter (currently unused and must be 0) */, + /*out*/ void **delegate /* Pointer where to store the function pointer result */); + +// Signature of delegate returned by load_assembly_and_get_function_pointer_fn when delegate_type_name == null (default) +typedef int (CORECLR_DELEGATE_CALLTYPE *component_entry_point_fn)(void *arg, int32_t arg_size_in_bytes); + +typedef int (CORECLR_DELEGATE_CALLTYPE *get_function_pointer_fn)( + const char_t *type_name /* Assembly qualified type name */, + const char_t *method_name /* Public static method name compatible with delegateType */, + const char_t *delegate_type_name /* Assembly qualified delegate type name or null, + or UNMANAGEDCALLERSONLY_METHOD if the method is marked with + the UnmanagedCallersOnlyAttribute. */, + void *load_context /* Extensibility parameter (currently unused and must be 0) */, + void *reserved /* Extensibility parameter (currently unused and must be 0) */, + /*out*/ void **delegate /* Pointer where to store the function pointer result */); + +#endif // __CORECLR_DELEGATES_H__ diff --git a/modules/mono/thirdparty/hostfxr.h b/modules/mono/thirdparty/hostfxr.h new file mode 100644 index 0000000000..591a8ebbea --- /dev/null +++ b/modules/mono/thirdparty/hostfxr.h @@ -0,0 +1,323 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __HOSTFXR_H__ +#define __HOSTFXR_H__ + +#include <stddef.h> +#include <stdint.h> + +#if defined(_WIN32) + #define HOSTFXR_CALLTYPE __cdecl + #ifdef _WCHAR_T_DEFINED + typedef wchar_t char_t; + #else + typedef unsigned short char_t; + #endif +#else + #define HOSTFXR_CALLTYPE + typedef char char_t; +#endif + +enum hostfxr_delegate_type +{ + hdt_com_activation, + hdt_load_in_memory_assembly, + hdt_winrt_activation, + hdt_com_register, + hdt_com_unregister, + hdt_load_assembly_and_get_function_pointer, + hdt_get_function_pointer, +}; + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_startupinfo_fn)( + const int argc, + const char_t **argv, + const char_t *host_path, + const char_t *dotnet_root, + const char_t *app_path); +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_bundle_startupinfo_fn)( + const int argc, + const char_t** argv, + const char_t* host_path, + const char_t* dotnet_root, + const char_t* app_path, + int64_t bundle_header_offset); + +typedef void(HOSTFXR_CALLTYPE *hostfxr_error_writer_fn)(const char_t *message); + +// +// Sets a callback which is to be used to write errors to. +// +// Parameters: +// error_writer +// A callback function which will be invoked every time an error is to be reported. +// Or nullptr to unregister previously registered callback and return to the default behavior. +// Return value: +// The previously registered callback (which is now unregistered), or nullptr if no previous callback +// was registered +// +// The error writer is registered per-thread, so the registration is thread-local. On each thread +// only one callback can be registered. Subsequent registrations overwrite the previous ones. +// +// By default no callback is registered in which case the errors are written to stderr. +// +// Each call to the error writer is sort of like writing a single line (the EOL character is omitted). +// Multiple calls to the error writer may occur for one failure. +// +// If the hostfxr invokes functions in hostpolicy as part of its operation, the error writer +// will be propagated to hostpolicy for the duration of the call. This means that errors from +// both hostfxr and hostpolicy will be reporter through the same error writer. +// +typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE *hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer); + +typedef void* hostfxr_handle; +struct hostfxr_initialize_parameters +{ + size_t size; + const char_t *host_path; + const char_t *dotnet_root; +}; + +// +// Initializes the hosting components for a dotnet command line running an application +// +// Parameters: +// argc +// Number of argv arguments +// argv +// Command-line arguments for running an application (as if through the dotnet executable). +// Only command-line arguments which are accepted by runtime installation are supported, SDK/CLI commands are not supported. +// For example 'app.dll app_argument_1 app_argument_2`. +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initialized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// HostInvalidState - Hosting components are already initialized +// +// This function parses the specified command-line arguments to determine the application to run. It will +// then find the corresponding .runtimeconfig.json and .deps.json with which to resolve frameworks and +// dependencies and prepare everything needed to load the runtime. +// +// This function only supports arguments for running an application. It does not support SDK commands. +// +// This function does not load the runtime. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_dotnet_command_line_fn)( + int argc, + const char_t **argv, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); + +// +// Initializes the hosting components using a .runtimeconfig.json file +// +// Parameters: +// runtime_config_path +// Path to the .runtimeconfig.json file +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initialized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// Success_HostAlreadyInitialized - Config is compatible with already initialized hosting components +// Success_DifferentRuntimeProperties - Config has runtime properties that differ from already initialized hosting components +// CoreHostIncompatibleConfig - Config is incompatible with already initialized hosting components +// +// This function will process the .runtimeconfig.json to resolve frameworks and prepare everything needed +// to load the runtime. It will only process the .deps.json from frameworks (not any app/component that +// may be next to the .runtimeconfig.json). +// +// This function does not load the runtime. +// +// If called when the runtime has already been loaded, this function will check if the specified runtime +// config is compatible with the existing runtime. +// +// Both Success_HostAlreadyInitialized and Success_DifferentRuntimeProperties codes are considered successful +// initializations. In the case of Success_DifferentRuntimeProperties, it is left to the consumer to verify that +// the difference in properties is acceptable. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_runtime_config_fn)( + const char_t *runtime_config_path, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); + +// +// Gets the runtime property value for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Out parameter. Pointer to a buffer with the property value. +// +// Return value: +// The error code result. +// +// The buffer pointed to by value is owned by the host context. The lifetime of the buffer is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// property value for the active host context. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + /*out*/ const char_t **value); + +// +// Sets the value of a runtime property for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Value to set +// +// Return value: +// The error code result. +// +// Setting properties is only supported for the first host context, before the runtime has been loaded. +// +// If the property already exists in the host context, it will be overwritten. If value is nullptr, the +// property will be removed. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_set_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + const char_t *value); + +// +// Gets all the runtime properties for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// count +// [in] Size of the keys and values buffers +// [out] Number of properties returned (size of keys/values buffers used). If the input value is too +// small or keys/values is nullptr, this is populated with the number of available properties +// keys +// Array of pointers to buffers with runtime property keys +// values +// Array of pointers to buffers with runtime property values +// +// Return value: +// The error code result. +// +// The buffers pointed to by keys and values are owned by the host context. The lifetime of the buffers is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// properties for the active host context. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_properties_fn)( + const hostfxr_handle host_context_handle, + /*inout*/ size_t * count, + /*out*/ const char_t **keys, + /*out*/ const char_t **values); + +// +// Load CoreCLR and run the application for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// If the app was successfully run, the exit code of the application. Otherwise, the error code result. +// +// The host_context_handle must have been initialized using hostfxr_initialize_for_dotnet_command_line. +// +// This function will not return until the managed application exits. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_run_app_fn)(const hostfxr_handle host_context_handle); + +// +// Gets a typed delegate from the currently loaded CoreCLR or from a newly created one. +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// type +// Type of runtime delegate requested +// delegate +// An out parameter that will be assigned the delegate. +// +// Return value: +// The error code result. +// +// If the host_context_handle was initialized using hostfxr_initialize_for_runtime_config, +// then all delegate types are supported. +// If the host_context_handle was initialized using hostfxr_initialize_for_dotnet_command_line, +// then only the following delegate types are currently supported: +// hdt_load_assembly_and_get_function_pointer +// hdt_get_function_pointer +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_delegate_fn)( + const hostfxr_handle host_context_handle, + enum hostfxr_delegate_type type, + /*out*/ void **delegate); + +// +// Closes an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// The error code result. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_close_fn)(const hostfxr_handle host_context_handle); + +struct hostfxr_dotnet_environment_sdk_info +{ + size_t size; + const char_t* version; + const char_t* path; +}; + +typedef void(HOSTFXR_CALLTYPE* hostfxr_get_dotnet_environment_info_result_fn)( + const struct hostfxr_dotnet_environment_info* info, + void* result_context); + +struct hostfxr_dotnet_environment_framework_info +{ + size_t size; + const char_t* name; + const char_t* version; + const char_t* path; +}; + +struct hostfxr_dotnet_environment_info +{ + size_t size; + + const char_t* hostfxr_version; + const char_t* hostfxr_commit_hash; + + size_t sdk_count; + const struct hostfxr_dotnet_environment_sdk_info* sdks; + + size_t framework_count; + const struct hostfxr_dotnet_environment_framework_info* frameworks; +}; + +#endif //__HOSTFXR_H__ |