summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-02-27 21:57:52 +0100
committerIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-08-22 03:36:51 +0200
commit4b90d162502d65f20a89331898cd8a0b3eea8fe2 (patch)
tree3e1efc5a5638676aabe32e67f3ed342a8dbe808a
parent18f805b3aad2be838a7396f18d4ebd99182b6935 (diff)
C#: Initial NativeAOT support
This commit adds initial support for games exported as NativeAOT shared libraries. At this moment, the NativeAOT runtime is experimental. Additionally, Godot is not trim-safe as it still makes some use of reflection. For the time being, a rd.xml file is needed to prevent code triming: ``` <Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata"> <Application> <Assembly Name="GodotSharp" Dynamic="Required All" /> <Assembly Name="GAME_ASSEMBLY" Dynamic="Required All" /> </Application> </Directives> ``` These are the csproj changes for publishing: ``` <PropertyGroup> <NativeLib>Shared</NativeLib> </PropertyGroup> <ItemGroup> <RdXmlFile Include="rd.xml" /> <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" /> </ItemGroup> ``` More info: - https://github.com/dotnet/runtimelab/blob/feature/NativeAOT/docs/using-nativeaot/compiling.md - https://github.com/dotnet/runtimelab/tree/feature/NativeAOT/samples/NativeLibrary - https://github.com/dotnet/runtimelab/blob/feature/NativeAOT/docs/using-nativeaot/rd-xml-format.md
-rw-r--r--modules/mono/csharp_script.cpp4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs11
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/Main.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs14
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs25
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp106
-rw-r--r--modules/mono/mono_gd/gd_mono.h1
10 files changed, 133 insertions, 54 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 134dd92078..f74c32ba04 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -2179,7 +2179,9 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
if (exports_invalidated)
#endif
{
+#ifdef TOOLS_ENABLED
exports_invalidated = false;
+#endif
changed = true;
@@ -2222,6 +2224,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
}
});
+#ifdef TOOLS_ENABLED
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++) {
@@ -2233,6 +2236,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
p_script->exported_members_defval_cache[name] = value;
}
});
+#endif
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
index 497a1b908c..54da6218f3 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
@@ -26,14 +26,16 @@ namespace GodotPlugins.Game
{
internal static partial class Main
{
- [UnmanagedCallersOnly]
- private static godot_bool InitializeFromGameProject(IntPtr outManagedCallbacks)
+ [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, GodotDllImportResolver.OnResolveDllImport);
+ NativeLibrary.SetDllImportResolver(coreApiAssembly, dllImportResolver);
ManagedCallbacks.Create(outManagedCallbacks);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 221deede2f..506c0ec067 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -197,10 +197,6 @@ namespace GodotTools.Build
// Logger
AddLoggerArgument(buildInfo, arguments);
- // Trimming is not supported for dynamically loaded assemblies, as is our case with self hosting:
- // https://github.com/dotnet/runtime/blob/main/docs/design/features/native-hosting.md#incompatible-with-trimming
- arguments.Add("-p:PublishTrimmed=false");
-
// Custom properties
foreach (string customProperty in buildInfo.CustomProperties)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index edf3eeb7fa..8d88734ead 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -134,7 +134,16 @@ namespace GodotTools.Export
throw new Exception("Failed to build project");
}
- if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpEditor.ProjectAssemblyName}.dll")))
+ string soExt = ridOS switch
+ {
+ OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
+ OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib",
+ _ => "so"
+ };
+
+ if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpEditor.ProjectAssemblyName}.dll"))
+ // NativeAOT shared library output
+ && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpEditor.ProjectAssemblyName}.{soExt}")))
{
throw new NotSupportedException(
"Publish succeeded but project assembly not found in the output directory");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index 50c5dc96a7..62140d41bc 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -44,7 +44,7 @@ namespace GodotTools.Utils
public const string HTML5 = "javascript";
}
- private static class DotNetOS
+ public static class DotNetOS
{
public const string Win = "win";
public const string OSX = "osx";
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
index 0cb9e57530..2a2e147eaa 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
@@ -20,22 +20,26 @@ namespace GodotPlugins
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(godot_bool editorHint,
+ private static unsafe godot_bool InitializeFromEngine(IntPtr godotDllHandle, godot_bool editorHint,
PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks)
{
try
{
+ _dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport;
+
SharedAssemblies.Add(CoreApiAssembly.GetName());
- NativeLibrary.SetDllImportResolver(CoreApiAssembly, GodotDllImportResolver.OnResolveDllImport);
+ NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver);
if (editorHint.ToBool())
{
_editorApiAssembly = Assembly.Load("GodotSharpEditor");
SharedAssemblies.Add(_editorApiAssembly.GetName());
- NativeLibrary.SetDllImportResolver(_editorApiAssembly, GodotDllImportResolver.OnResolveDllImport);
+ NativeLibrary.SetDllImportResolver(_editorApiAssembly, _dllImportResolver);
}
*pluginsCallbacks = new()
@@ -97,7 +101,7 @@ namespace GodotPlugins
var assembly = LoadPlugin(assemblyPath);
- NativeLibrary.SetDllImportResolver(assembly, GodotDllImportResolver.OnResolveDllImport);
+ NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!);
var method = assembly.GetType("GodotTools.GodotSharpEditor")?
.GetMethod("InternalCreateInstance",
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 71736a65c7..61987c6466 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -39,7 +39,12 @@ namespace Godot.Bridge
try
{
- Type nativeType = TypeGetProxyClass(nativeTypeName);
+ 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(
@@ -171,12 +176,9 @@ namespace Godot.Bridge
}
}
- private static unsafe Type TypeGetProxyClass(godot_string_name* nativeTypeName)
+ private static Type TypeGetProxyClass(string nativeTypeNameStr)
{
// Performance is not critical here as this will be replaced with a generated dictionary.
- using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
- NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName)));
- string nativeTypeNameStr = stringName.ToString();
if (nativeTypeNameStr[0] == '_')
nativeTypeNameStr = nativeTypeNameStr.Substring(1);
@@ -186,7 +188,7 @@ namespace Godot.Bridge
if (wrapperType == null)
{
wrapperType = AppDomain.CurrentDomain.GetAssemblies()
- .First(a => a.GetName().Name == "GodotSharpEditor")
+ .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor")?
.GetType("Godot." + nativeTypeNameStr);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs
index c4a90625c3..5579992d2b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs
@@ -6,9 +6,16 @@ using System.Runtime.InteropServices;
namespace Godot.NativeInterop
{
- public static class GodotDllImportResolver
+ public class GodotDllImportResolver
{
- public static IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
+ private IntPtr _internalHandle;
+
+ public GodotDllImportResolver(IntPtr internalHandle)
+ {
+ _internalHandle = internalHandle;
+ }
+
+ public IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName == "__Internal")
{
@@ -18,7 +25,7 @@ namespace Godot.NativeInterop
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- return Linux.dlopen(IntPtr.Zero, Linux.RTLD_LAZY);
+ return _internalHandle;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
@@ -40,18 +47,6 @@ namespace Godot.NativeInterop
public static extern IntPtr dlopen(IntPtr path, int mode);
}
- private static class Linux
- {
- // libdl.so was resulting in DllNotFoundException, for some reason...
- // libcoreclr.so should work with both CoreCLR and the .NET Core version of Mono.
- private const string SystemLibrary = "libcoreclr.so";
-
- 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";
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 8499d30ba4..d57ad2831a 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -48,6 +48,9 @@
#include <coreclr_delegates.h>
#include <hostfxr.h>
+#ifdef UNIX_ENABLED
+#include <dlfcn.h>
+#endif
// TODO mobile
#if 0
@@ -168,18 +171,24 @@ String find_hostfxr() {
#else
#if defined(WINDOWS_ENABLED)
- return GodotSharpDirs::get_api_assemblies_dir()
- .plus_file("hostfxr.dll");
+ String probe_path = GodotSharpDirs::get_api_assemblies_dir()
+ .plus_file("hostfxr.dll");
#elif defined(MACOS_ENABLED)
- return GodotSharpDirs::get_api_assemblies_dir()
- .plus_file("libhostfxr.dylib");
+ String probe_path = GodotSharpDirs::get_api_assemblies_dir()
+ .plus_file("libhostfxr.dylib");
#elif defined(UNIX_ENABLED)
- return GodotSharpDirs::get_api_assemblies_dir()
- .plus_file("libhostfxr.so");
+ String probe_path = GodotSharpDirs::get_api_assemblies_dir()
+ .plus_file("libhostfxr.so");
#else
#error "Platform not supported (yet?)"
#endif
+ if (FileAccess::exists(probe_path)) {
+ return probe_path;
+ }
+
+ return String();
+
#endif
}
@@ -285,11 +294,21 @@ load_assembly_and_get_function_pointer_fn initialize_hostfxr_self_contained(
#endif
#ifdef TOOLS_ENABLED
-using godot_plugins_initialize_fn = bool (*)(bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *);
+using godot_plugins_initialize_fn = bool (*)(void *, bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *);
#else
-using godot_plugins_initialize_fn = bool (*)(GDMonoCache::ManagedCallbacks *);
+using godot_plugins_initialize_fn = bool (*)(void *, GDMonoCache::ManagedCallbacks *);
#endif
+static String get_assembly_name() {
+ 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";
+ }
+
+ return appname_safe;
+}
+
#ifdef TOOLS_ENABLED
godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
@@ -320,15 +339,9 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime
}
#else
godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
- 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";
- }
-
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
- String assembly_name = appname_safe;
+ String assembly_name = get_assembly_name();
HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
.plus_file(assembly_name + ".dll"));
@@ -351,6 +364,38 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime
return godot_plugins_initialize;
}
+
+godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) {
+ String assembly_name = get_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
+
+ 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) {
+ return nullptr;
+ }
+
+ void *lib = r_aot_dll_handle;
+
+ void *symbol = nullptr;
+
+ 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;
+ }
+
+ return nullptr;
+}
#endif
} // namespace
@@ -377,25 +422,46 @@ void GDMono::initialize() {
_init_godot_api_hashes();
+ godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+
if (!load_hostfxr(hostfxr_dll_handle)) {
+#if !defined(TOOLS_ENABLED)
+ godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);
+
+ 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
}
- godot_plugins_initialize_fn godot_plugins_initialize =
- initialize_hostfxr_and_godot_plugins(runtime_initialized);
- ERR_FAIL_NULL(godot_plugins_initialize);
+ if (!is_native_aot) {
+ godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized);
+ ERR_FAIL_NULL(godot_plugins_initialize);
+ }
GDMonoCache::ManagedCallbacks managed_callbacks;
+ void *godot_dll_handle = nullptr;
+
+#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
+
#ifdef TOOLS_ENABLED
gdmono::PluginCallbacks plugin_callbacks_res;
- bool init_ok = godot_plugins_initialize(Engine::get_singleton()->is_editor_hint(),
+ bool init_ok = godot_plugins_initialize(godot_dll_handle,
+ Engine::get_singleton()->is_editor_hint(),
&plugin_callbacks_res, &managed_callbacks);
ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
plugin_callbacks = plugin_callbacks_res;
#else
- bool init_ok = godot_plugins_initialize(&managed_callbacks);
+ bool init_ok = godot_plugins_initialize(godot_dll_handle, &managed_callbacks);
ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
#endif
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 66ed331b67..301782575c 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -61,6 +61,7 @@ class GDMono {
bool finalizing_scripts_domain;
void *hostfxr_dll_handle = nullptr;
+ bool is_native_aot = false;
#ifdef TOOLS_ENABLED
bool _load_project_assembly();