From 88e367a4066773a6fbfe2ea25dc2e81d2035d791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Rold=C3=A1n=20Etcheverry?= Date: Tue, 28 Dec 2021 23:25:16 +0100 Subject: C#/netcore: Add base desktop game export implementation This base implementation is still very barebones but it defines the path for how exporting will work (at least when embedding the .NET runtime). Many manual steps are still needed, which should be automatized in the future. For example, in addition to the API assemblies, now you also need to copy the GodotPlugins assembly to each game project. --- modules/mono/glue/GodotSharp/GodotPlugins/Main.cs | 93 ++++------------------ .../GodotSharp/GodotPlugins/PluginLoadContext.cs | 2 - .../Core/Attributes/GodotMethodAttribute.cs | 24 ------ .../GodotSharp/Core/Bridge/CSharpInstanceBridge.cs | 27 +++++-- .../GodotSharp/Core/Bridge/ManagedCallbacks.cs | 9 ++- .../GodotSharp/Core/Bridge/ScriptManagerBridge.cs | 66 +-------------- .../Core/NativeInterop/GodotDllImportResolver.cs | 64 +++++++++++++++ .../glue/GodotSharp/GodotSharp/Core/Object.base.cs | 34 +++++--- .../glue/GodotSharp/GodotSharp/GodotSharp.csproj | 2 +- .../GodotSharp/Properties/AssemblyInfo.cs | 1 - 10 files changed, 136 insertions(+), 186 deletions(-) delete mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs (limited to 'modules/mono/glue/GodotSharp') diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs index 9f938373c4..0cb9e57530 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -4,6 +4,7 @@ using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; +using Godot.Bridge; using Godot.NativeInterop; namespace GodotPlugins @@ -13,6 +14,7 @@ namespace GodotPlugins private static readonly List SharedAssemblies = new(); private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly; private static Assembly? _editorApiAssembly; + private static Assembly? _projectAssembly; private static readonly AssemblyLoadContext MainLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? @@ -20,67 +22,59 @@ namespace GodotPlugins // Right now we do it this way for simplicity as hot-reload is disabled. It will need to be changed later. [UnmanagedCallersOnly] - internal static unsafe godot_bool Initialize(godot_bool editorHint, - PluginsCallbacks* pluginsCallbacks, Godot.Bridge.ManagedCallbacks* managedCallbacks) + // ReSharper disable once UnusedMember.Local + private static unsafe godot_bool InitializeFromEngine(godot_bool editorHint, + PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks) { try { SharedAssemblies.Add(CoreApiAssembly.GetName()); + NativeLibrary.SetDllImportResolver(CoreApiAssembly, GodotDllImportResolver.OnResolveDllImport); if (editorHint.ToBool()) { _editorApiAssembly = Assembly.Load("GodotSharpEditor"); SharedAssemblies.Add(_editorApiAssembly.GetName()); + NativeLibrary.SetDllImportResolver(_editorApiAssembly, GodotDllImportResolver.OnResolveDllImport); } - NativeLibrary.SetDllImportResolver(CoreApiAssembly, OnResolveDllImport); - *pluginsCallbacks = new() { LoadProjectAssemblyCallback = &LoadProjectAssembly, LoadToolsAssemblyCallback = &LoadToolsAssembly, }; - *managedCallbacks = Godot.Bridge.ManagedCallbacks.Create(); + *managedCallbacks = ManagedCallbacks.Create(); return godot_bool.True; } catch (Exception e) { Console.Error.WriteLine(e); - *pluginsCallbacks = default; - *managedCallbacks = default; return false.ToGodotBool(); } } [StructLayout(LayoutKind.Sequential)] - internal struct PluginsCallbacks + private struct PluginsCallbacks { public unsafe delegate* unmanaged LoadProjectAssemblyCallback; public unsafe delegate* unmanaged LoadToolsAssemblyCallback; } [UnmanagedCallersOnly] - internal static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath) + private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath) { try { - string assemblyPath = new(nAssemblyPath); + if (_projectAssembly != null) + return godot_bool.True; // Already loaded - var assembly = LoadPlugin(assemblyPath); - - var method = CoreApiAssembly.GetType("Godot.Bridge.ScriptManagerBridge")? - .GetMethod("LookupScriptsInAssembly", - BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + string assemblyPath = new(nAssemblyPath); - if (method == null) - { - throw new MissingMethodException("Godot.Bridge.ScriptManagerBridge", - "LookupScriptsInAssembly"); - } + _projectAssembly = LoadPlugin(assemblyPath); - method.Invoke(null, new object[] { assembly }); + ScriptManagerBridge.LookupScriptsInAssembly(_projectAssembly); return godot_bool.True; } @@ -92,7 +86,7 @@ namespace GodotPlugins } [UnmanagedCallersOnly] - internal static unsafe IntPtr LoadToolsAssembly(char* nAssemblyPath) + private static unsafe IntPtr LoadToolsAssembly(char* nAssemblyPath) { try { @@ -103,7 +97,7 @@ namespace GodotPlugins var assembly = LoadPlugin(assemblyPath); - NativeLibrary.SetDllImportResolver(assembly, OnResolveDllImport); + NativeLibrary.SetDllImportResolver(assembly, GodotDllImportResolver.OnResolveDllImport); var method = assembly.GetType("GodotTools.GodotSharpEditor")? .GetMethod("InternalCreateInstance", @@ -140,58 +134,5 @@ namespace GodotPlugins var loadContext = new PluginLoadContext(assemblyPath, sharedAssemblies, MainLoadContext); return loadContext.LoadFromAssemblyName(new AssemblyName(assemblyName)); } - - public static IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) - { - if (libraryName == "__Internal") - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Win32.GetModuleHandle(IntPtr.Zero); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return Linux.dlopen(IntPtr.Zero, Linux.RTLD_LAZY); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return MacOS.dlopen(IntPtr.Zero, MacOS.RTLD_LAZY); - } - } - - return IntPtr.Zero; - } - - // ReSharper disable InconsistentNaming - private static class MacOS - { - private const string SystemLibrary = "/usr/lib/libSystem.dylib"; - - public const int RTLD_LAZY = 1; - - [DllImport(SystemLibrary)] - public static extern IntPtr dlopen(IntPtr path, int mode); - } - - private static class 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"; - - [DllImport(SystemLibrary)] - public static extern IntPtr GetModuleHandle(IntPtr lpModuleName); - } - // ReSharper restore InconsistentNaming } } diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs index 1b969716aa..982549fff7 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs @@ -15,8 +15,6 @@ namespace GodotPlugins public PluginLoadContext(string pluginPath, ICollection sharedAssemblies, AssemblyLoadContext mainLoadContext) { - Console.WriteLine(pluginPath); - Console.Out.Flush(); _resolver = new AssemblyDependencyResolver(pluginPath); _sharedAssemblies = sharedAssemblies; _mainLoadContext = mainLoadContext; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs deleted file mode 100644 index 8d4ff0fdb7..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Godot -{ - /// - /// An attribute for a method. - /// - [AttributeUsage(AttributeTargets.Method)] - internal class GodotMethodAttribute : Attribute - { - private string methodName; - - public string MethodName { get { return methodName; } } - - /// - /// Constructs a new GodotMethodAttribute instance. - /// - /// The name of the method. - public GodotMethodAttribute(string methodName) - { - this.methodName = methodName; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs index f28d7b1c51..266df07d63 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs @@ -21,12 +21,8 @@ namespace Godot.Bridge return false.ToGodotBool(); } - NativeFuncs.godotsharp_string_name_as_string(out godot_string dest, CustomUnsafe.AsRef(method)); - string methodStr; - using (dest) - methodStr = Marshaling.ConvertStringToManaged(dest); - - bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args), + bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), + new NativeVariantPtrArgs(args), argCount, out godot_variant retValue); if (!methodInvoked) @@ -168,5 +164,24 @@ namespace Godot.Bridge *outValid = false.ToGodotBool(); } } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool HasMethodUnknownParams(IntPtr godotObjectGCHandle, godot_string_name* method) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + return false.ToGodotBool(); + + return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.DebugPrintUnhandledException(e); + return false.ToGodotBool(); + } + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs index bd939ef27d..fb1efa0ac8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -5,7 +5,7 @@ using Godot.NativeInterop; namespace Godot.Bridge { [StructLayout(LayoutKind.Sequential)] - internal unsafe struct ManagedCallbacks + public unsafe struct ManagedCallbacks { // @formatter:off public delegate* unmanaged SignalAwaiter_SignalCallback; @@ -19,7 +19,6 @@ namespace Godot.Bridge public delegate* unmanaged ScriptManagerBridge_RaiseEventSignal; public delegate* unmanaged ScriptManagerBridge_GetScriptSignalList; public delegate* unmanaged ScriptManagerBridge_HasScriptSignal; - public delegate* unmanaged ScriptManagerBridge_HasMethodUnknownParams; public delegate* unmanaged ScriptManagerBridge_ScriptIsOrInherits; public delegate* unmanaged ScriptManagerBridge_AddScriptBridge; public delegate* unmanaged ScriptManagerBridge_RemoveScriptBridge; @@ -30,6 +29,7 @@ namespace Godot.Bridge public delegate* unmanaged CSharpInstanceBridge_Get; public delegate* unmanaged CSharpInstanceBridge_CallDispose; public delegate* unmanaged CSharpInstanceBridge_CallToString; + public delegate* unmanaged CSharpInstanceBridge_HasMethodUnknownParams; public delegate* unmanaged GCHandleBridge_FreeGCHandle; public delegate* unmanaged DebuggingUtils_InstallTraceListener; public delegate* unmanaged Dispatcher_InitializeDefaultGodotTaskScheduler; @@ -52,7 +52,6 @@ namespace Godot.Bridge ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal, ScriptManagerBridge_GetScriptSignalList = &ScriptManagerBridge.GetScriptSignalList, ScriptManagerBridge_HasScriptSignal = &ScriptManagerBridge.HasScriptSignal, - ScriptManagerBridge_HasMethodUnknownParams = &ScriptManagerBridge.HasMethodUnknownParams, ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits, ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge, ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge, @@ -63,6 +62,7 @@ namespace Godot.Bridge CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get, CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose, CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString, + CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams, GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle, DebuggingUtils_InstallTraceListener = &DebuggingUtils.InstallTraceListener, Dispatcher_InitializeDefaultGodotTaskScheduler = &Dispatcher.InitializeDefaultGodotTaskScheduler, @@ -70,5 +70,8 @@ namespace Godot.Bridge // @formatter:on }; } + + public static void Create(IntPtr outManagedCallbacks) + => *(ManagedCallbacks*)outManagedCallbacks = Create(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index e87b7f4d4b..b86ced55cb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -8,7 +8,7 @@ using Godot.NativeInterop; namespace Godot.Bridge { - internal static class ScriptManagerBridge + public static class ScriptManagerBridge { private static System.Collections.Generic.Dictionary _scriptLookupMap = new(); private static System.Collections.Generic.Dictionary _scriptBridgeMap = new(); @@ -212,7 +212,7 @@ namespace Godot.Bridge // Called from GodotPlugins // ReSharper disable once UnusedMember.Local - private static void LookupScriptsInAssembly(Assembly assembly) + public static void LookupScriptsInAssembly(Assembly assembly) { static void LookupScriptForClass(Type type) { @@ -439,68 +439,6 @@ namespace Godot.Bridge } } - [UnmanagedCallersOnly] - internal static unsafe godot_bool HasMethodUnknownParams(IntPtr scriptPtr, godot_string* method, - godot_bool deep) - { - try - { - // Performance is not critical here as this will be replaced with source generators. - if (!_scriptBridgeMap.TryGetValue(scriptPtr, out var scriptType)) - return false.ToGodotBool(); - - string methodStr = Marshaling.ConvertStringToManaged(*method); - - if (deep.ToBool()) - { - Type top = scriptType; - Type native = Object.InternalGetClassNativeBase(scriptType); - - while (top != null && top != native) - { - var methodInfo = top.GetMethod(methodStr, - BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public); - - if (methodInfo != null) - return true.ToGodotBool(); - - top = top.BaseType; - } - - top = native; - Type typeOfSystemObject = typeof(System.Object); - while (top != null && top != typeOfSystemObject) - { - bool found = top.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public) - .Where(m => m.GetCustomAttributes(false).OfType() - .Where(a => a.MethodName == methodStr) - .Any()) - .Any(); - - if (found) - return true.ToGodotBool(); - - top = top.BaseType; - } - - return false.ToGodotBool(); - } - else - { - var methodInfo = scriptType.GetMethod(methodStr, BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public); - return (methodInfo != null).ToGodotBool(); - } - } - catch (Exception e) - { - ExceptionUtils.DebugUnhandledException(e); - return false.ToGodotBool(); - } - } - [UnmanagedCallersOnly] internal static godot_bool ScriptIsOrInherits(IntPtr scriptPtr, IntPtr scriptPtrMaybeBase) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs new file mode 100644 index 0000000000..c4a90625c3 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs @@ -0,0 +1,64 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Godot.NativeInterop +{ + public static class GodotDllImportResolver + { + public static IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName == "__Internal") + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Win32.GetModuleHandle(IntPtr.Zero); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Linux.dlopen(IntPtr.Zero, Linux.RTLD_LAZY); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MacOS.dlopen(IntPtr.Zero, MacOS.RTLD_LAZY); + } + } + + return IntPtr.Zero; + } + + // ReSharper disable InconsistentNaming + private static class MacOS + { + private const string SystemLibrary = "/usr/lib/libSystem.dylib"; + + public const int RTLD_LAZY = 1; + + [DllImport(SystemLibrary)] + public static extern IntPtr dlopen(IntPtr path, int mode); + } + + private static class 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"; + + [DllImport(SystemLibrary)] + public static extern IntPtr GetModuleHandle(IntPtr lpModuleName); + } + // ReSharper restore InconsistentNaming + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index a3a4c2599e..6255ffbbc7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -8,7 +8,7 @@ namespace Godot public partial class Object : IDisposable { private bool _disposed = false; - private Type _cachedType = typeof(Object); + private static readonly Type CachedType = typeof(Object); internal IntPtr NativePtr; private bool _memoryOwn; @@ -19,21 +19,31 @@ namespace Godot /// Constructs a new . /// public Object() : this(false) + { + unsafe + { + _ConstructAndInitialize(NativeCtor, NativeName, CachedType, refCounted: false); + } + } + + internal unsafe void _ConstructAndInitialize( + delegate* unmanaged nativeCtor, + StringName nativeName, + Type cachedType, + bool refCounted + ) { if (NativePtr == IntPtr.Zero) { - unsafe - { - NativePtr = NativeCtor(); - } + NativePtr = nativeCtor(); InteropUtils.TieManagedToUnmanaged(this, NativePtr, - NativeName, refCounted: false, GetType(), _cachedType); + nativeName, refCounted, GetType(), cachedType); } else { InteropUtils.TieManagedToUnmanagedWithPreSetup(this, NativePtr, - GetType(), _cachedType); + GetType(), cachedType); } _weakReferenceToSelf = DisposablesTracker.RegisterGodotObject(this); @@ -191,8 +201,14 @@ namespace Godot internal static bool InternalIsClassNativeBase(Type t) { - var assemblyName = t.Assembly.GetName(); - return assemblyName.Name == "GodotSharp" || assemblyName.Name == "GodotSharpEditor"; + // Check whether the type is declared in the GodotSharp or GodotSharpEditor assemblies + var typeAssembly = t.Assembly; + + if (typeAssembly == CachedType.Assembly) + return true; + + var typeAssemblyName = t.Assembly.GetName(); + return typeAssemblyName.Name == "GodotSharpEditor"; } // ReSharper disable once VirtualMemberNeverOverridden.Global diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index f1a397f8fa..b7fbb81f9c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -33,7 +33,6 @@ - @@ -68,6 +67,7 @@ + diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs index dbd98d0afb..da6f293871 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs @@ -1,4 +1,3 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("GodotSharpEditor")] -[assembly: InternalsVisibleTo("GodotPlugins")] -- cgit v1.2.3