diff options
author | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2021-09-12 20:21:15 +0200 |
---|---|---|
committer | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-08-22 03:35:59 +0200 |
commit | 513ee857a938c466e0f7146f66db771b9c6e2024 (patch) | |
tree | 9b05c59a6d63f8cc18460e69c1a782ef6fe2713f /modules/mono/glue/GodotSharp | |
parent | 5e37d073bb86492e8c415964ffd554a2fa08920d (diff) |
C#: Restructure code prior move to .NET Core
The main focus here was to remove the majority of code that relied on
Mono's embedding APIs, specially the reflection APIs. The embedding
APIs we still use are the bare minimum we need for things to work.
A lot of code was moved to C#. We no longer deal with any managed
objects (`MonoObject*`, and such) in native code, and all marshaling
is done in C#.
The reason for restructuring the code and move away from embedding APIs
is that once we move to .NET Core, we will be limited by the much more
minimal .NET hosting.
PERFORMANCE REGRESSIONS
-----------------------
Some parts of the code were written with little to no concern about
performance. This includes code that calls into script methods and
accesses script fields, properties and events.
The reason for this is that all of that will be moved to source
generators, so any work prior to that would be a waste of time.
DISABLED FEATURES
-----------------
Some code was removed as it no longer makes sense (or won't make sense
in the future).
Other parts were commented out with `#if 0`s and TODO warnings because
it doesn't make much sense to work on them yet as those parts will
change heavily when we switch to .NET Core but also when we start
introducing source generators.
As such, the following features were disabled temporarily:
- Assembly-reloading (will be done with ALCs in .NET Core).
- Properties/fields exports and script method listing (will be
handled by source generators in the future).
- Exception logging in the editor and stack info for errors.
- Exporting games.
- Building of C# projects. We no longer copy the Godot API assemblies
to the project directory, so MSBuild won't be able to find them. The
idea is to turn them into NuGet packages in the future, which could
also be obtained from local NuGet sources during development.
Diffstat (limited to 'modules/mono/glue/GodotSharp')
24 files changed, 1143 insertions, 132 deletions
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index 893ab81e5d..a2a97e0a3e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -14,7 +14,7 @@ namespace Godot.Collections /// </summary> public sealed class Array : IList, IDisposable { - internal godot_array NativeValue; + public godot_array NativeValue; /// <summary> /// Constructs a new empty <see cref="Array"/>. @@ -307,7 +307,7 @@ namespace Godot.Collections { using godot_string str = default; NativeFuncs.godotsharp_array_to_string(ref NativeValue, &str); - return Marshaling.mono_string_from_godot(&str); + return Marshaling.mono_string_from_godot(str); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs index 2febf37f05..b7d633517a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Godot { /// <summary> @@ -8,25 +10,28 @@ namespace Godot [AttributeUsage(AttributeTargets.Assembly)] public class AssemblyHasScriptsAttribute : Attribute { - private readonly bool requiresLookup; - private readonly System.Type[] scriptTypes; + public bool RequiresLookup { get; } + public Type[]? ScriptTypes { get; } /// <summary> /// Constructs a new AssemblyHasScriptsAttribute instance. /// </summary> public AssemblyHasScriptsAttribute() { - requiresLookup = true; + RequiresLookup = true; + ScriptTypes = null; } /// <summary> /// Constructs a new AssemblyHasScriptsAttribute instance. /// </summary> /// <param name="scriptTypes">The specified type(s) of scripts.</param> - public AssemblyHasScriptsAttribute(System.Type[] scriptTypes) + public AssemblyHasScriptsAttribute(Type[] scriptTypes) { - requiresLookup = false; - this.scriptTypes = scriptTypes; + RequiresLookup = false; + ScriptTypes = scriptTypes; } } } + +#nullable restore diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs index 3ebb6612de..2c8a53ae1c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs @@ -8,7 +8,7 @@ namespace Godot [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class ScriptPathAttribute : Attribute { - private string path; + public string Path { get; } /// <summary> /// Constructs a new ScriptPathAttribute instance. @@ -16,7 +16,7 @@ namespace Godot /// <param name="path">The file path to the script</param> public ScriptPathAttribute(string path) { - this.path = path; + Path = path; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs new file mode 100644 index 0000000000..16fde2a900 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs @@ -0,0 +1,123 @@ +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + internal static class CSharpInstanceBridge + { + private static unsafe void Call(IntPtr godotObjectGCHandle, godot_string_name* method, + godot_variant** args, int argCount, godot_variant_call_error* ref_callError, godot_variant* r_ret) + { + // Performance is not critical here as this will be replaced with source generators. + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + { + *r_ret = default; + (*ref_callError).error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL; + return; + } + + using godot_string dest = default; + NativeFuncs.godotsharp_string_name_as_string(&dest, method); + string methodStr = Marshaling.mono_string_from_godot(dest); + + bool methodInvoked = godotObject.InternalGodotScriptCall(methodStr, args, argCount, out godot_variant outRet); + + if (!methodInvoked) + { + *r_ret = default; + // This is important, as it tells Object::call that no method was called. + // Otherwise, it would prevent Object::call from calling native methods. + (*ref_callError).error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD; + return; + } + + *r_ret = outRet; + } + + private static unsafe bool Set(IntPtr godotObjectGCHandle, godot_string_name* name, godot_variant* value) + { + // Performance is not critical here as this will be replaced with source generators. + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + throw new InvalidOperationException(); + + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(name)); + + if (godotObject.InternalGodotScriptSetFieldOrPropViaReflection(nameManaged.ToString(), value)) + return true; + + object valueManaged = Marshaling.variant_to_mono_object(value); + + return godotObject._Set(nameManaged, valueManaged); + } + + private static unsafe bool Get(IntPtr godotObjectGCHandle, godot_string_name* name, godot_variant* r_retValue) + { + // Performance is not critical here as this will be replaced with source generators. + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + throw new InvalidOperationException(); + + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(name)); + + if (godotObject.InternalGodotScriptGetFieldOrPropViaReflection(nameManaged.ToString(), + out godot_variant outRet)) + { + *r_retValue = outRet; + return true; + } + + object ret = godotObject._Get(nameManaged); + + if (ret == null) + { + *r_retValue = default; + return false; + } + + *r_retValue = Marshaling.mono_object_to_variant(ret); + return true; + } + + private static void CallDispose(IntPtr godotObjectGCHandle, bool okIfNull) + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (okIfNull) + godotObject?.Dispose(); + else + godotObject!.Dispose(); + } + + private static unsafe void CallToString(IntPtr godotObjectGCHandle, godot_string* r_res, bool* r_valid) + { + var self = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (self == null) + { + *r_res = default; + *r_valid = false; + return; + } + + var resultStr = self.ToString(); + + if (resultStr == null) + { + *r_res = default; + *r_valid = false; + return; + } + + *r_res = Marshaling.mono_string_to_godot(resultStr); + *r_valid = true; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs new file mode 100644 index 0000000000..aa9e434b07 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.InteropServices; + +namespace Godot.Bridge +{ + internal static class GCHandleBridge + { + private static void FreeGCHandle(IntPtr gcHandlePtr) + => GCHandle.FromIntPtr(gcHandlePtr).Free(); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs new file mode 100644 index 0000000000..a39da68a02 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -0,0 +1,501 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using Godot.Collections; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + internal static class ScriptManagerBridge + { + private static System.Collections.Generic.Dictionary<string, ScriptLookupInfo> _scriptLookupMap = new(); + private static System.Collections.Generic.Dictionary<IntPtr, Type> _scriptBridgeMap = new(); + + private struct ScriptLookupInfo + { + public string ClassNamespace { get; private set; } + public string ClassName { get; private set; } + public Type ScriptType { get; private set; } + + public ScriptLookupInfo(string classNamespace, string className, Type scriptType) + { + ClassNamespace = classNamespace; + ClassName = className; + ScriptType = scriptType; + } + }; + + internal static void FrameCallback() + { + Dispatcher.DefaultGodotTaskScheduler?.Activate(); + } + + internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName, + IntPtr godotObject) + { + Type nativeType = TypeGetProxyClass(nativeTypeName); + var obj = (Object)FormatterServices.GetUninitializedObject(nativeType); + + obj.NativePtr = godotObject; + + var ctor = nativeType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + null, Type.EmptyTypes, null); + _ = ctor!.Invoke(obj, null); + + return GCHandle.ToIntPtr(GCHandle.Alloc(obj)); + } + + internal static unsafe void CreateManagedForGodotObjectScriptInstance(IntPtr scriptPtr, IntPtr godotObject, + godot_variant** args, int argCount) + { + // Performance is not critical here as this will be replaced with source generators. + Type scriptType = _scriptBridgeMap[scriptPtr]; + var obj = (Object)FormatterServices.GetUninitializedObject(scriptType); + + obj.NativePtr = godotObject; + + var ctor = scriptType + .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(c => c.GetParameters().Length == argCount) + .FirstOrDefault(); + + if (ctor == null) + { + if (argCount == 0) + { + throw new MissingMemberException( + $"Cannot create script instance. The class '{scriptType.FullName}' does not define a parameterless constructor."); + } + else + { + throw new MissingMemberException( + $"The class '{scriptType.FullName}' does not define a constructor that takes x parameters."); + } + } + + var parameters = ctor.GetParameters(); + int paramCount = parameters.Length; + + object[] invokeParams = new object[paramCount]; + + for (int i = 0; i < paramCount; i++) + { + invokeParams[i] = Marshaling.variant_to_mono_object_of_type( + args[i], parameters[i].ParameterType); + } + + ctor.Invoke(obj, invokeParams); + } + + private static unsafe void GetScriptNativeName(IntPtr scriptPtr, godot_string_name* r_res) + { + // Performance is not critical here as this will be replaced with source generators. + if (!_scriptBridgeMap.TryGetValue(scriptPtr, out var scriptType)) + { + *r_res = default; + return; + } + + var native = Object.InternalGetClassNativeBase(scriptType); + + var field = native?.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.Public | BindingFlags.NonPublic); + + if (field == null) + { + *r_res = default; + return; + } + + var nativeName = (StringName)field.GetValue(null); + + *r_res = NativeFuncs.godotsharp_string_name_new_copy(nativeName.NativeValue); + } + + private static void SetGodotObjectPtr(IntPtr gcHandlePtr, IntPtr newPtr) + { + var target = (Object)GCHandle.FromIntPtr(gcHandlePtr).Target; + if (target != null) + target.NativePtr = newPtr; + } + + private static unsafe Type TypeGetProxyClass(godot_string_name* nativeTypeName) + { + // 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(nativeTypeName)); + string nativeTypeNameStr = stringName.ToString(); + + if (nativeTypeNameStr[0] == '_') + nativeTypeNameStr = nativeTypeNameStr.Substring(1); + + Type wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr); + + if (wrapperType == null) + { + wrapperType = AppDomain.CurrentDomain.GetAssemblies() + .First(a => a.GetName().Name == "GodotSharpEditor") + .GetType("Godot." + nativeTypeNameStr); + } + + static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed; + + if (wrapperType != null && IsStatic(wrapperType)) + { + // A static class means this is a Godot singleton class. If an instance is needed we use Godot.Object. + return typeof(Object); + } + + return wrapperType; + } + + internal static void LookupScriptsInAssembly(Assembly assembly) + { + static void LookupScriptForClass(Type type) + { + var scriptPathAttr = type.GetCustomAttributes(inherit: false) + .OfType<ScriptPathAttribute>() + .FirstOrDefault(); + + if (scriptPathAttr == null) + return; + + _scriptLookupMap[scriptPathAttr.Path] = new ScriptLookupInfo(type.Namespace, type.Name, type); + } + + var assemblyHasScriptsAttr = assembly.GetCustomAttributes(inherit: false) + .OfType<AssemblyHasScriptsAttribute>() + .FirstOrDefault(); + + if (assemblyHasScriptsAttr == null) + return; + + if (assemblyHasScriptsAttr.RequiresLookup) + { + // This is supported for scenarios where specifying all types would be cumbersome, + // such as when disabling C# source generators (for whatever reason) or when using a + // language other than C# that has nothing similar to source generators to automate it. + + var typeOfGodotObject = typeof(Object); + + foreach (var type in assembly.GetTypes()) + { + if (type.IsNested) + continue; + + if (!typeOfGodotObject.IsAssignableFrom(type)) + continue; + + LookupScriptForClass(type); + } + } + else + { + // This is the most likely scenario as we use C# source generators + + var scriptTypes = assemblyHasScriptsAttr.ScriptTypes; + + if (scriptTypes != null) + { + for (int i = 0; i < scriptTypes.Length; i++) + { + LookupScriptForClass(scriptTypes[i]); + } + } + } + } + + internal static unsafe void RaiseEventSignal(IntPtr ownerGCHandlePtr, + godot_string_name* eventSignalName, godot_variant** args, int argCount, bool* r_ownerIsNull) + { + var owner = (Object)GCHandle.FromIntPtr(ownerGCHandlePtr).Target; + + if (owner == null) + { + *r_ownerIsNull = true; + return; + } + + *r_ownerIsNull = false; + + owner.InternalRaiseEventSignal(eventSignalName, args, argCount); + } + + internal static unsafe void GetScriptSignalList(IntPtr scriptPtr, godot_dictionary* r_retSignals) + { + // Performance is not critical here as this will be replaced with source generators. + using var signals = new Dictionary(); + + Type top = _scriptBridgeMap[scriptPtr]; + Type native = Object.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + // Legacy signals + + foreach (var signalDelegate in top + .GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public) + .Where(nestedType => typeof(Delegate).IsAssignableFrom(nestedType)) + .Where(@delegate => @delegate.GetCustomAttributes().OfType<SignalAttribute>().Any())) + { + var invokeMethod = signalDelegate.GetMethod("Invoke"); + + if (invokeMethod == null) + throw new MissingMethodException(signalDelegate.FullName, "Invoke"); + + var signalParams = new Collections.Array(); + + foreach (var parameters in invokeMethod.GetParameters()) + { + var paramType = Marshaling.managed_to_variant_type( + parameters.ParameterType, out bool nilIsVariant); + signalParams.Add(new Dictionary() + { + { "name", parameters.Name }, + { "type", paramType }, + { "nil_is_variant", nilIsVariant } + }); + } + + signals.Add(signalDelegate.Name, signalParams); + } + + // Event signals + + var foundEventSignals = top.GetEvents( + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public) + .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any()) + .Select(ev => ev.Name); + + var fields = top.GetFields( + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + + foreach (var eventSignalField in fields + .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType)) + .Where(f => foundEventSignals.Contains(f.Name))) + { + var delegateType = eventSignalField.FieldType; + var invokeMethod = delegateType.GetMethod("Invoke"); + + if (invokeMethod == null) + throw new MissingMethodException(delegateType.FullName, "Invoke"); + + var signalParams = new Collections.Array(); + + foreach (var parameters in invokeMethod.GetParameters()) + { + var paramType = Marshaling.managed_to_variant_type( + parameters.ParameterType, out bool nilIsVariant); + signalParams.Add(new Dictionary() + { + { "name", parameters.Name }, + { "type", paramType }, + { "nil_is_variant", nilIsVariant } + }); + } + + signals.Add(eventSignalField.Name, signalParams); + } + + top = top.BaseType; + } + + *r_retSignals = NativeFuncs.godotsharp_dictionary_new_copy(signals.NativeValue); + } + + internal static unsafe bool HasScriptSignal(IntPtr scriptPtr, godot_string* signalName) + { + // Performance is not critical here as this will be replaced with source generators. + using var signals = new Dictionary(); + + string signalNameStr = Marshaling.mono_string_from_godot(*signalName); + + Type top = _scriptBridgeMap[scriptPtr]; + Type native = Object.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + // Legacy signals + + if (top + .GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public) + .Where(nestedType => typeof(Delegate).IsAssignableFrom(nestedType)) + .Where(@delegate => @delegate.GetCustomAttributes().OfType<SignalAttribute>().Any()) + .Any(signalDelegate => signalDelegate.Name == signalNameStr) + ) + { + return true; + } + + // Event signals + + if (top.GetEvents( + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public) + .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any()) + .Any(eventSignal => eventSignal.Name == signalNameStr) + ) + { + return true; + } + + top = top.BaseType; + } + + return false; + } + + internal static unsafe bool HasMethodUnknownParams(IntPtr scriptPtr, godot_string* method, bool deep) + { + // Performance is not critical here as this will be replaced with source generators. + if (!_scriptBridgeMap.TryGetValue(scriptPtr, out var scriptType)) + return false; + + string methodStr = Marshaling.mono_string_from_godot(*method); + + if (deep) + { + 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; + + top = top.BaseType; + } + + return false; + } + else + { + var methodInfo = scriptType.GetMethod(methodStr, BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + return methodInfo != null; + } + } + + internal static bool ScriptIsOrInherits(IntPtr scriptPtr, IntPtr scriptPtrMaybeBase) + { + if (!_scriptBridgeMap.TryGetValue(scriptPtr, out var scriptType)) + return false; + + if (!_scriptBridgeMap.TryGetValue(scriptPtrMaybeBase, out var maybeBaseType)) + return false; + + return scriptType == maybeBaseType || maybeBaseType.IsAssignableFrom(scriptType); + } + + internal static unsafe bool AddScriptBridge(IntPtr scriptPtr, godot_string* scriptPath) + { + string scriptPathStr = Marshaling.mono_string_from_godot(*scriptPath); + + if (!_scriptLookupMap.TryGetValue(scriptPathStr, out var lookupInfo)) + return false; + + _scriptBridgeMap.Add(scriptPtr, lookupInfo.ScriptType); + + return true; + } + + internal static void AddScriptBridgeWithType(IntPtr scriptPtr, Type scriptType) + => _scriptBridgeMap.Add(scriptPtr, scriptType); + + internal static void RemoveScriptBridge(IntPtr scriptPtr) + => _scriptBridgeMap.Remove(scriptPtr); + + internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, bool* r_tool, + godot_dictionary* r_rpcFunctionsDest) + { + // Performance is not critical here as this will be replaced with source generators. + var scriptType = _scriptBridgeMap[scriptPtr]; + + *r_tool = scriptType.GetCustomAttributes(inherit: false) + .OfType<ToolAttribute>() + .Any(); + + if (!*r_tool && scriptType.IsNested) + { + *r_tool = scriptType.DeclaringType?.GetCustomAttributes(inherit: false) + .OfType<ToolAttribute>() + .Any() ?? false; + } + + if (!*r_tool && scriptType.Assembly.GetName().Name == "GodotTools") + *r_tool = true; + + // RPC functions + + Dictionary<string, Dictionary> rpcFunctions = new(); + + Type top = _scriptBridgeMap[scriptPtr]; + Type native = Object.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + foreach (var method in top.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public)) + { + if (method.IsStatic) + continue; + + string methodName = method.Name; + + if (rpcFunctions.ContainsKey(methodName)) + continue; + + var rpcAttr = method.GetCustomAttributes(inherit: false) + .OfType<RPCAttribute>().FirstOrDefault(); + + if (rpcAttr == null) + continue; + + var rpcConfig = new Dictionary(); + + rpcConfig["rpc_mode"] = (long)rpcAttr.Mode; + rpcConfig["call_local"] = rpcAttr.CallLocal; + rpcConfig["transfer_mode"] = (long)rpcAttr.TransferMode; + rpcConfig["channel"] = rpcAttr.TransferChannel; + + rpcFunctions.Add(methodName, rpcConfig); + } + + top = top.BaseType; + } + + *r_rpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new_copy(((Dictionary)rpcFunctions).NativeValue); + } + + internal static unsafe bool SwapGCHandleForType(IntPtr oldGCHandlePtr, IntPtr* r_newGCHandlePtr, + bool createWeak) + { + var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr); + + object target = oldGCHandle.Target; + + if (target == null) + { + oldGCHandle.Free(); + *r_newGCHandlePtr = IntPtr.Zero; + return false; // Called after the managed side was collected, so nothing to do here + } + + // Release the current weak handle and replace it with a strong handle. + var newGCHandle = GCHandle.Alloc(target, createWeak ? GCHandleType.Weak : GCHandleType.Normal); + + oldGCHandle.Free(); + *r_newGCHandlePtr = GCHandle.ToIntPtr(newGCHandle); + return true; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 187d910f9f..2a562d4d48 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -11,10 +11,6 @@ namespace Godot { internal static class DelegateUtils { - // TODO: Move somewhere else once we need to for things other than delegates - internal static void FreeGCHandle(IntPtr delegateGCHandle) - => GCHandle.FromIntPtr(delegateGCHandle).Free(); - internal static bool DelegateEquals(IntPtr delegateGCHandleA, IntPtr delegateGCHandleB) { var @delegateA = (Delegate)GCHandle.FromIntPtr(delegateGCHandleA).Target; @@ -22,14 +18,14 @@ namespace Godot return @delegateA == @delegateB; } - internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, godot_variant** args, uint argc, godot_variant* ret) + internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, godot_variant** args, uint argc, + godot_variant* ret) { // TODO: Optimize var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target; var managedArgs = new object[argc]; var parameterInfos = @delegate.Method.GetParameters(); - var paramsLength = parameterInfos.Length; if (argc != paramsLength) @@ -260,7 +256,8 @@ namespace Godot } } - private static bool TryDeserializeDelegateWithGCHandle(Collections.Array serializedData, out IntPtr delegateGCHandle) + private static bool TryDeserializeDelegateWithGCHandle(Collections.Array serializedData, + out IntPtr delegateGCHandle) { bool res = TryDeserializeDelegate(serializedData, out Delegate @delegate); delegateGCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(@delegate)); @@ -368,7 +365,8 @@ namespace Godot int valueBufferLength = reader.ReadInt32(); byte[] valueBuffer = reader.ReadBytes(valueBufferLength); - FieldInfo fieldInfo = targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public); + FieldInfo fieldInfo = + targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public); fieldInfo?.SetValue(recreatedTarget, GD.Bytes2Var(valueBuffer)); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index 932ee33fe3..8bc33837e6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -15,7 +15,7 @@ namespace Godot.Collections IDictionary, IDisposable { - internal godot_dictionary NativeValue; + public godot_dictionary NativeValue; /// <summary> /// Constructs a new empty <see cref="Dictionary"/>. @@ -319,7 +319,7 @@ namespace Godot.Collections { using godot_string str = default; NativeFuncs.godotsharp_dictionary_to_string(ref NativeValue, &str); - return Marshaling.mono_string_from_godot(&str); + return Marshaling.mono_string_from_godot(str); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs index 7922f38ac5..b939da8778 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System.Reflection; using System.Runtime.CompilerServices; using Godot.Collections; using Godot.NativeInterop; @@ -11,14 +11,64 @@ namespace Godot /// Returns a list of all nodes assigned to the given <paramref name="group"/>. /// </summary> /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> - public Array<T> GetNodesInGroup<T>(StringName group) where T : class + public unsafe Array<T> GetNodesInGroup<T>(StringName group) where T : class { - godot_array array; - godot_icall_SceneTree_get_nodes_in_group_Generic(GetPtr(this), ref group.NativeValue, typeof(T), out array); - return Array<T>.CreateTakingOwnershipOfDisposableValue(array); + var array = GetNodesInGroup(group); + + if (array.Count == 0) + return new Array<T>(array); + + var typeOfT = typeof(T); + bool nativeBase = InternalIsClassNativeBase(typeOfT); + + if (nativeBase) + { + // Native type + var field = typeOfT.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.Public | BindingFlags.NonPublic); + + var nativeName = (StringName)field!.GetValue(null); + godot_string_name nativeNameAux = nativeName.NativeValue; + godot_array inputAux = array.NativeValue; + godot_array filteredArray; + godotsharp_array_filter_godot_objects_by_native(&nativeNameAux, &inputAux, &filteredArray); + return Array<T>.CreateTakingOwnershipOfDisposableValue(filteredArray); + } + else + { + // Custom derived type + godot_array inputAux = array.NativeValue; + godot_array filteredArray; + godotsharp_array_filter_godot_objects_by_non_native(&inputAux, &filteredArray); + + var filteredArrayWrapped = Array.CreateTakingOwnershipOfDisposableValue(filteredArray); + + // Re-use first array as its size is the same or greater than the filtered one + var resWrapped = new Array<T>(array); + + int j = 0; + for (int i = 0; i < filteredArrayWrapped.Count; i++) + { + if (filteredArrayWrapped[i] is T t) + { + resWrapped[j] = t; + j++; + } + } + + // Remove trailing elements, since this was re-used + resWrapped.Resize(j); + + return resWrapped; + } } [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, ref godot_string_name group, Type elemType, out godot_array dest); + internal extern unsafe void godotsharp_array_filter_godot_objects_by_native(godot_string_name* p_native_name, + godot_array* p_input, godot_array* r_output); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern unsafe void godotsharp_array_filter_godot_objects_by_non_native(godot_array* p_input, + godot_array* r_output); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 9d237b8d93..f428100ff7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -525,7 +525,7 @@ namespace Godot using var whatGodotArray = Marshaling.mono_array_to_Array(what); using godot_string ret = default; NativeFuncs.godotsharp_str(&whatGodotArray, &ret); - return Marshaling.mono_string_from_godot(&ret); + return Marshaling.mono_string_from_godot(ret); } /// <summary> @@ -588,7 +588,7 @@ namespace Godot using var variant = Marshaling.mono_object_to_variant(var); using godot_string ret = default; NativeFuncs.godotsharp_var2str(&variant, &ret); - return Marshaling.mono_string_from_godot(&ret); + return Marshaling.mono_string_from_godot(ret); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs index 9ccac1faaf..78a9d0fe9d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs @@ -17,10 +17,7 @@ namespace Godot public override void Fail(string message, string detailMessage) { GD.PrintErr("Assertion failed: ", message); - if (detailMessage != null) - { - GD.PrintErr(" Details: ", detailMessage); - } + GD.PrintErr(" Details: ", detailMessage); try { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index 865863cd3e..d8931f8348 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -13,7 +13,7 @@ namespace Godot.NativeInterop { [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_bool + public struct godot_bool { public byte _value; @@ -25,7 +25,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_ref : IDisposable + public struct godot_ref : IDisposable { internal IntPtr _reference; @@ -41,7 +41,7 @@ namespace Godot.NativeInterop } [SuppressMessage("ReSharper", "InconsistentNaming")] - internal enum godot_variant_call_error_error + public enum godot_variant_call_error_error { GODOT_CALL_ERROR_CALL_OK = 0, GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD, @@ -53,16 +53,16 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_variant_call_error + public struct godot_variant_call_error { - godot_variant_call_error_error error; - int argument; - int expected; + public godot_variant_call_error_error error; + public int argument; + public int expected; } [StructLayout(LayoutKind.Explicit)] // ReSharper disable once InconsistentNaming - internal struct godot_variant : IDisposable + public struct godot_variant : IDisposable { // Variant.Type is generated as an enum of type long, so we can't use for the field as it must only take 32-bits. [FieldOffset(0)] private int _typeField; @@ -162,7 +162,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_string : IDisposable + public struct godot_string : IDisposable { internal IntPtr _ptr; @@ -180,7 +180,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_string_name : IDisposable + public struct godot_string_name : IDisposable { internal IntPtr _data; @@ -201,7 +201,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_node_path : IDisposable + public struct godot_node_path : IDisposable { internal IntPtr _data; @@ -222,7 +222,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Explicit)] // ReSharper disable once InconsistentNaming - internal struct godot_signal : IDisposable + public struct godot_signal : IDisposable { [FieldOffset(0)] public godot_string_name _name; @@ -241,7 +241,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Explicit)] // ReSharper disable once InconsistentNaming - internal struct godot_callable : IDisposable + public struct godot_callable : IDisposable { [FieldOffset(0)] public godot_string_name _method; @@ -265,7 +265,7 @@ namespace Godot.NativeInterop // be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine). [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_array : IDisposable + public struct godot_array : IDisposable { internal unsafe ArrayPrivate* _p; @@ -304,7 +304,7 @@ namespace Godot.NativeInterop // be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine). [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_dictionary : IDisposable + public struct godot_dictionary : IDisposable { internal IntPtr _p; @@ -319,7 +319,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_byte_array : IDisposable + public struct godot_packed_byte_array : IDisposable { internal IntPtr _writeProxy; internal unsafe byte* _ptr; @@ -337,7 +337,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_int32_array : IDisposable + public struct godot_packed_int32_array : IDisposable { internal IntPtr _writeProxy; internal unsafe int* _ptr; @@ -355,7 +355,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_int64_array : IDisposable + public struct godot_packed_int64_array : IDisposable { internal IntPtr _writeProxy; internal unsafe long* _ptr; @@ -373,7 +373,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_float32_array : IDisposable + public struct godot_packed_float32_array : IDisposable { internal IntPtr _writeProxy; internal unsafe float* _ptr; @@ -391,7 +391,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_float64_array : IDisposable + public struct godot_packed_float64_array : IDisposable { internal IntPtr _writeProxy; internal unsafe double* _ptr; @@ -409,7 +409,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_string_array : IDisposable + public struct godot_packed_string_array : IDisposable { internal IntPtr _writeProxy; internal unsafe godot_string* _ptr; @@ -427,7 +427,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_vector2_array : IDisposable + public struct godot_packed_vector2_array : IDisposable { internal IntPtr _writeProxy; internal unsafe Vector2* _ptr; @@ -445,7 +445,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_vector3_array : IDisposable + public struct godot_packed_vector3_array : IDisposable { internal IntPtr _writeProxy; internal unsafe Vector3* _ptr; @@ -463,7 +463,7 @@ namespace Godot.NativeInterop [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming - internal struct godot_packed_color_array : IDisposable + public struct godot_packed_color_array : IDisposable { internal IntPtr _writeProxy; internal unsafe Color* _ptr; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs index 08d49bb937..5d53006140 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs @@ -1,5 +1,9 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.Bridge; + +// ReSharper disable InconsistentNaming namespace Godot.NativeInterop { @@ -7,21 +11,105 @@ namespace Godot.NativeInterop { public static Object UnmanagedGetManaged(IntPtr unmanaged) { - // TODO: Move to C# - return internal_unmanaged_get_managed(unmanaged); + // The native pointer may be null + if (unmanaged == IntPtr.Zero) + return null; + + IntPtr gcHandlePtr; + bool has_cs_script_instance = false; + + // First try to get the tied managed instance from a CSharpInstance script instance + + unsafe + { + gcHandlePtr = unmanaged_get_script_instance_managed(unmanaged, &has_cs_script_instance); + } + + if (gcHandlePtr != IntPtr.Zero) + return (Object)GCHandle.FromIntPtr(gcHandlePtr).Target; + + // Otherwise, if the object has a CSharpInstance script instance, return null + + if (has_cs_script_instance) + return null; + + // If it doesn't have a CSharpInstance script instance, try with native instance bindings + + gcHandlePtr = unmanaged_get_instance_binding_managed(unmanaged); + + object target = gcHandlePtr != IntPtr.Zero ? GCHandle.FromIntPtr(gcHandlePtr).Target : null; + + if (target != null) + return (Object)target; + + // If the native instance binding GC handle target was collected, create a new one + + gcHandlePtr = unmanaged_instance_binding_create_managed(unmanaged, gcHandlePtr); + + return gcHandlePtr != IntPtr.Zero ? (Object)GCHandle.FromIntPtr(gcHandlePtr).Target : null; } [MethodImpl(MethodImplOptions.InternalCall)] - private static extern Object internal_unmanaged_get_managed(IntPtr unmanaged); + private static extern unsafe IntPtr unmanaged_get_script_instance_managed(IntPtr p_unmanaged, + bool* r_has_cs_script_instance); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr unmanaged_get_instance_binding_managed(IntPtr p_unmanaged); - public static void TieManagedToUnmanaged(Object managed, IntPtr unmanaged) + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr unmanaged_instance_binding_create_managed(IntPtr p_unmanaged, + IntPtr oldGCHandlePtr); + + public static void TieManagedToUnmanaged(Object managed, IntPtr unmanaged, + StringName nativeName, bool refCounted, Type type, Type nativeType) { - // TODO: Move to C# - internal_tie_managed_to_unmanaged(managed, unmanaged); + var gcHandle = GCHandle.Alloc(managed, refCounted ? GCHandleType.Weak : GCHandleType.Normal); + + if (type == nativeType) + { + unsafe + { + godot_string_name nativeNameAux = nativeName.NativeValue; + internal_tie_native_managed_to_unmanaged(GCHandle.ToIntPtr(gcHandle), unmanaged, + &nativeNameAux, refCounted); + } + } + else + { + IntPtr scriptPtr = internal_new_csharp_script(); + + ScriptManagerBridge.AddScriptBridgeWithType(scriptPtr, type); + + // IMPORTANT: This must be called after AddScriptWithTypeBridge + internal_tie_user_managed_to_unmanaged(GCHandle.ToIntPtr(gcHandle), unmanaged, + scriptPtr, refCounted); + } } [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_tie_managed_to_unmanaged(Object managed, IntPtr unmanaged); + private static extern unsafe void internal_tie_native_managed_to_unmanaged(IntPtr gcHandleIntPtr, + IntPtr unmanaged, godot_string_name* nativeName, bool refCounted); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_tie_user_managed_to_unmanaged(IntPtr gcHandleIntPtr, + IntPtr unmanaged, IntPtr scriptPtr, bool refCounted); + + public static void TieManagedToUnmanagedWithPreSetup(Object managed, IntPtr unmanaged, + Type type, Type nativeType) + { + if (type == nativeType) + return; + + var strongGCHandle = GCHandle.Alloc(managed, GCHandleType.Normal); + internal_tie_managed_to_unmanaged_with_pre_setup(GCHandle.ToIntPtr(strongGCHandle), unmanaged); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_tie_managed_to_unmanaged_with_pre_setup( + IntPtr gcHandleIntPtr, IntPtr unmanaged); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr internal_new_csharp_script(); public static unsafe Object EngineGetSingleton(string name) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index e0819b2857..eae644af85 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -10,14 +10,8 @@ namespace Godot.NativeInterop { // We want to use full name qualifiers here even if redundant for clarity [SuppressMessage("ReSharper", "RedundantNameQualifier")] - internal static class Marshaling + public static class Marshaling { - public static unsafe void SetFieldValue(FieldInfo fieldInfo, object obj, godot_variant* value) - { - var valueObj = variant_to_mono_object_of_type(value, fieldInfo.FieldType); - fieldInfo.SetValue(obj, valueObj); - } - public static Variant.Type managed_to_variant_type(Type type, out bool r_nil_is_variant) { r_nil_is_variant = false; @@ -242,10 +236,6 @@ namespace Godot.NativeInterop return mono_object_to_variant_impl(p_obj); } - // TODO: Only called from C++. Remove once no longer needed. - private static unsafe void mono_object_to_variant_out(object p_obj, bool p_fail_with_err, godot_variant* r_ret) - => *r_ret = mono_object_to_variant_impl(p_obj, p_fail_with_err); - private static unsafe godot_variant mono_object_to_variant_impl(object p_obj, bool p_fail_with_err = true) { if (p_obj == null) @@ -457,7 +447,7 @@ namespace Godot.NativeInterop // TODO: Validate element type is compatible with Variant #if NET var nativeGodotArray = - mono_array_to_Array(System.Runtime.InteropServices.CollectionsMarshal.AsSpan((dynamic)p_obj)); + (godot_array)mono_array_to_Array(System.Runtime.InteropServices.CollectionsMarshal.AsSpan((dynamic)p_obj)); #else // With .NET Standard we need a package reference for Microsoft.CSharp in order to // use dynamic, so we have this workaround for now until we switch to .NET 5/6. @@ -500,12 +490,12 @@ namespace Godot.NativeInterop case Variant.Type.String: { // We avoid the internal call if the stored type is the same we want. - return mono_string_from_godot(&(*p_var)._data._m_string); + return mono_string_from_godot((*p_var)._data._m_string); } default: { using godot_string godotString = NativeFuncs.godotsharp_variant_as_string(p_var); - return mono_string_from_godot(&godotString); + return mono_string_from_godot(godotString); } } } @@ -877,7 +867,7 @@ namespace Godot.NativeInterop #endif } case Variant.Type.String: - return mono_string_from_godot(&(*p_var)._data._m_string); + return mono_string_from_godot((*p_var)._data._m_string); case Variant.Type.Vector2: return (*p_var)._data._m_vector2; case Variant.Type.Vector2i: @@ -1007,14 +997,14 @@ namespace Godot.NativeInterop } } - public static unsafe string mono_string_from_godot(godot_string* p_string) + public static unsafe string mono_string_from_godot(in godot_string p_string) { - if ((*p_string)._ptr == IntPtr.Zero) + if (p_string._ptr == IntPtr.Zero) return string.Empty; const int sizeOfChar32 = 4; - byte* bytes = (byte*)(*p_string)._ptr; - int size = (*p_string).Size; + byte* bytes = (byte*)p_string._ptr; + int size = p_string.Size; if (size == 0) return string.Empty; size -= 1; // zero at the end @@ -1283,7 +1273,7 @@ namespace Godot.NativeInterop int size = (*p_array).Size; var array = new string[size]; for (int i = 0; i < size; i++) - array[i] = mono_string_from_godot(&buffer[i]); + array[i] = mono_string_from_godot(buffer[i]); return array; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index adbf5eb9b6..73ac837fe1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -10,7 +10,7 @@ namespace Godot.NativeInterop // The attribute is not available with .NET Core and it's not needed there. [System.Security.SuppressUnmanagedCodeSecurity] #endif - internal static unsafe partial class NativeFuncs + public static unsafe partial class NativeFuncs { private const string GodotDllName = "__Internal"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs index 6001b3a0de..089883c7e8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; namespace Godot.NativeInterop { - internal static unsafe partial class NativeFuncs + public static unsafe partial class NativeFuncs { public static godot_string_name godotsharp_string_name_new_copy(godot_string_name* src) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs index 67f9e23893..91ba864687 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; namespace Godot.NativeInterop { - internal static class VariantUtils + public static class VariantUtils { public static godot_variant CreateFromRID(RID from) => new() {_type = Variant.Type.Rid, _data = {_m_rid = from}}; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs index 541364b281..824f29558f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs @@ -42,7 +42,7 @@ namespace Godot /// </example> public sealed class NodePath : IDisposable { - internal godot_node_path NativeValue; + public godot_node_path NativeValue; ~NodePath() { @@ -140,7 +140,7 @@ namespace Godot godot_node_path src = NativeValue; NativeFuncs.godotsharp_node_path_as_string(&dest, &src); using (dest) - return Marshaling.mono_string_from_godot(&dest); + return Marshaling.mono_string_from_godot(dest); } /// <summary> @@ -179,7 +179,7 @@ namespace Godot { using godot_string names = default; NativeFuncs.godotsharp_node_path_get_concatenated_names(ref NativeValue, &names); - return Marshaling.mono_string_from_godot(&names); + return Marshaling.mono_string_from_godot(names); } /// <summary> @@ -197,7 +197,7 @@ namespace Godot { using godot_string subNames = default; NativeFuncs.godotsharp_node_path_get_concatenated_subnames(ref NativeValue, &subNames); - return Marshaling.mono_string_from_godot(&subNames); + return Marshaling.mono_string_from_godot(subNames); } /// <summary> @@ -217,7 +217,7 @@ namespace Godot { using godot_string name = default; NativeFuncs.godotsharp_node_path_get_name(ref NativeValue, idx, &name); - return Marshaling.mono_string_from_godot(&name); + return Marshaling.mono_string_from_godot(name); } /// <summary> @@ -240,7 +240,7 @@ namespace Godot { using godot_string subName = default; NativeFuncs.godotsharp_node_path_get_subname(ref NativeValue, idx, &subName); - return Marshaling.mono_string_from_godot(&subName); + return Marshaling.mono_string_from_godot(subName); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index 7bbaef62fa..763483a11f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; using Godot.NativeInterop; @@ -7,6 +9,7 @@ namespace Godot public partial class Object : IDisposable { private bool _disposed = false; + private Type _cachedType = typeof(Object); internal IntPtr NativePtr; internal bool MemoryOwn; @@ -21,12 +24,18 @@ namespace Godot #if NET unsafe { - ptr = NativeCtor(); + NativePtr = NativeCtor(); } #else NativePtr = _gd__invoke_class_constructor(NativeCtor); #endif - NativeInterop.InteropUtils.TieManagedToUnmanaged(this, NativePtr); + InteropUtils.TieManagedToUnmanaged(this, NativePtr, + NativeName, refCounted: false, GetType(), _cachedType); + } + else + { + InteropUtils.TieManagedToUnmanagedWithPreSetup(this, NativePtr, + GetType(), _cachedType); } _InitializeGodotScriptInstanceInternals(); @@ -34,12 +43,32 @@ namespace Godot internal void _InitializeGodotScriptInstanceInternals() { - godot_icall_Object_ConnectEventSignals(NativePtr); + // Performance is not critical here as this will be replaced with source generators. + Type top = GetType(); + Type native = InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + foreach (var eventSignal in top.GetEvents( + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public) + .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any())) + { + unsafe + { + using var eventSignalName = new StringName(eventSignal.Name); + godot_string_name eventSignalNameAux = eventSignalName.NativeValue; + godot_icall_Object_ConnectEventSignal(NativePtr, &eventSignalNameAux); + } + } + + top = top.BaseType; + } } internal Object(bool memoryOwn) { - this.MemoryOwn = memoryOwn; + MemoryOwn = memoryOwn; } /// <summary> @@ -85,11 +114,11 @@ namespace Godot if (MemoryOwn) { MemoryOwn = false; - godot_icall_RefCounted_Disposed(this, NativePtr, !disposing); + godot_icall_RefCounted_Disposed(NativePtr, !disposing); } else { - godot_icall_Object_Disposed(this, NativePtr); + godot_icall_Object_Disposed(NativePtr); } this.NativePtr = IntPtr.Zero; @@ -106,7 +135,7 @@ namespace Godot { using godot_string str = default; NativeFuncs.godotsharp_object_to_string(GetPtr(this), &str); - return Marshaling.mono_string_from_godot(&str); + return Marshaling.mono_string_from_godot(str); } /// <summary> @@ -141,13 +170,219 @@ namespace Godot return new SignalAwaiter(source, signal, this); } + internal static Type InternalGetClassNativeBase(Type t) + { + do + { + var assemblyName = t.Assembly.GetName(); + + if (assemblyName.Name == "GodotSharp") + return t; + + if (assemblyName.Name == "GodotSharpEditor") + return t; + } while ((t = t.BaseType) != null); + + return null; + } + + internal static bool InternalIsClassNativeBase(Type t) + { + var assemblyName = t.Assembly.GetName(); + return assemblyName.Name == "GodotSharp" || assemblyName.Name == "GodotSharpEditor"; + } + + internal unsafe bool InternalGodotScriptCallViaReflection(string method, godot_variant** args, int argCount, + out godot_variant ret) + { + // Performance is not critical here as this will be replaced with source generators. + Type top = GetType(); + Type native = InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var methodInfo = top.GetMethod(method, + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + + if (methodInfo != null) + { + var parameters = methodInfo.GetParameters(); + int paramCount = parameters.Length; + + if (argCount == paramCount) + { + object[] invokeParams = new object[paramCount]; + + for (int i = 0; i < paramCount; i++) + { + invokeParams[i] = Marshaling.variant_to_mono_object_of_type( + args[i], parameters[i].ParameterType); + } + + object retObj = methodInfo.Invoke(this, invokeParams); + + ret = Marshaling.mono_object_to_variant(retObj); + return true; + } + } + + top = top.BaseType; + } + + ret = default; + return false; + } + + internal unsafe bool InternalGodotScriptSetFieldOrPropViaReflection(string name, godot_variant* value) + { + // Performance is not critical here as this will be replaced with source generators. + Type top = GetType(); + Type native = InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var fieldInfo = top.GetField(name, + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + + if (fieldInfo != null) + { + object valueManaged = Marshaling.variant_to_mono_object_of_type(value, fieldInfo.FieldType); + fieldInfo.SetValue(this, valueManaged); + + return true; + } + + var propertyInfo = top.GetProperty(name, + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + + if (propertyInfo != null) + { + object valueManaged = Marshaling.variant_to_mono_object_of_type(value, propertyInfo.PropertyType); + propertyInfo.SetValue(this, valueManaged); + + return true; + } + + top = top.BaseType; + } + + return false; + } + + internal bool InternalGodotScriptGetFieldOrPropViaReflection(string name, out godot_variant value) + { + // Performance is not critical here as this will be replaced with source generators. + Type top = GetType(); + Type native = InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var fieldInfo = top.GetField(name, + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + + if (fieldInfo != null) + { + object valueManaged = fieldInfo.GetValue(this); + value = Marshaling.mono_object_to_variant(valueManaged); + return true; + } + + var propertyInfo = top.GetProperty(name, + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + + if (propertyInfo != null) + { + object valueManaged = propertyInfo.GetValue(this); + value = Marshaling.mono_object_to_variant(valueManaged); + return true; + } + + top = top.BaseType; + } + + value = default; + return false; + } + + internal unsafe void InternalRaiseEventSignal(godot_string_name* eventSignalName, godot_variant** args, + int argc) + { + // Performance is not critical here as this will be replaced with source generators. + + using var stringName = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(eventSignalName)); + string eventSignalNameStr = stringName.ToString(); + + Type top = GetType(); + Type native = InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var foundEventSignals = top.GetEvents( + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public) + .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any()) + .Select(ev => ev.Name); + + var fields = top.GetFields( + BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public); + + var eventSignalField = fields + .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType)) + .Where(f => foundEventSignals.Contains(f.Name)) + .FirstOrDefault(f => f.Name == eventSignalNameStr); + + if (eventSignalField != null) + { + var @delegate = (Delegate)eventSignalField.GetValue(this); + + if (@delegate == null) + continue; + + var delegateType = eventSignalField.FieldType; + + var invokeMethod = delegateType.GetMethod("Invoke"); + + if (invokeMethod == null) + throw new MissingMethodException(delegateType.FullName, "Invoke"); + + var parameterInfos = invokeMethod.GetParameters(); + var paramsLength = parameterInfos.Length; + + if (argc != paramsLength) + { + throw new InvalidOperationException( + $"The event delegate expects {paramsLength} arguments, but received {argc}."); + } + + var managedArgs = new object[argc]; + + for (uint i = 0; i < argc; i++) + { + managedArgs[i] = Marshaling.variant_to_mono_object_of_type( + args[i], parameterInfos[i].ParameterType); + } + + invokeMethod.Invoke(@delegate, managedArgs); + return; + } + + top = top.BaseType; + } + } + internal static unsafe IntPtr ClassDB_get_method(StringName type, string method) { IntPtr methodBind; fixed (char* methodChars = method) { - methodBind = NativeInterop.NativeFuncs - .godotsharp_method_bind_get_method(ref type.NativeValue, methodChars); + methodBind = NativeFuncs.godotsharp_method_bind_get_method(ref type.NativeValue, methodChars); } if (methodBind == IntPtr.Zero) @@ -157,11 +392,10 @@ namespace Godot } #if NET - internal static unsafe delegate* unmanaged<IntPtr> _gd__ClassDB_get_constructor(StringName type) + internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) { // for some reason the '??' operator doesn't support 'delegate*' - var nativeConstructor = NativeInterop.NativeFuncs - .godotsharp_get_class_constructor(ref type.NativeValue); + var nativeConstructor = NativeFuncs.godotsharp_get_class_constructor(ref type.NativeValue); if (nativeConstructor == null) throw new NativeConstructorNotFoundException(type); @@ -172,8 +406,7 @@ namespace Godot internal static IntPtr ClassDB_get_constructor(StringName type) { // for some reason the '??' operator doesn't support 'delegate*' - var nativeConstructor = NativeInterop.NativeFuncs - .godotsharp_get_class_constructor(ref type.NativeValue); + var nativeConstructor = NativeFuncs.godotsharp_get_class_constructor(ref type.NativeValue); if (nativeConstructor == IntPtr.Zero) throw new NativeConstructorNotFoundException(type); @@ -182,16 +415,17 @@ namespace Godot } internal static IntPtr _gd__invoke_class_constructor(IntPtr ctorFuncPtr) - => NativeInterop.NativeFuncs.godotsharp_invoke_class_constructor(ctorFuncPtr); + => NativeFuncs.godotsharp_invoke_class_constructor(ctorFuncPtr); #endif [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Object_Disposed(Object obj, IntPtr ptr); + internal static extern void godot_icall_Object_Disposed(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_RefCounted_Disposed(Object obj, IntPtr ptr, bool isFinalizer); + internal static extern void godot_icall_RefCounted_Disposed(IntPtr ptr, bool isFinalizer); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Object_ConnectEventSignals(IntPtr obj); + internal static extern unsafe void godot_icall_Object_ConnectEventSignal(IntPtr obj, + godot_string_name* eventSignal); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ScriptManager.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ScriptManager.cs deleted file mode 100644 index e92688f5bb..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/ScriptManager.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Godot -{ - internal class ScriptManager - { - internal static void FrameCallback() - { - Dispatcher.DefaultGodotTaskScheduler?.Activate(); - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs index e485207fb4..fd6636e410 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Godot.NativeInterop; namespace Godot @@ -12,11 +13,13 @@ namespace Godot public SignalAwaiter(Object source, StringName signal, Object target) { - godot_icall_SignalAwaiter_connect(Object.GetPtr(source), ref signal.NativeValue, Object.GetPtr(target), this); + godot_icall_SignalAwaiter_connect(Object.GetPtr(source), ref signal.NativeValue, + Object.GetPtr(target), GCHandle.ToIntPtr(GCHandle.Alloc(this))); } [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Error godot_icall_SignalAwaiter_connect(IntPtr source, ref godot_string_name signal, IntPtr target, SignalAwaiter awaiter); + internal static extern Error godot_icall_SignalAwaiter_connect(IntPtr source, ref godot_string_name signal, + IntPtr target, IntPtr awaiterHandlePtr); public bool IsCompleted => _completed; @@ -29,11 +32,30 @@ namespace Godot public IAwaiter<object[]> GetAwaiter() => this; - internal void SignalCallback(object[] args) + internal static unsafe void SignalCallback(IntPtr awaiterGCHandlePtr, + godot_variant** args, int argCount, + bool* r_awaiterIsNull) { - _completed = true; - _result = args; - _action?.Invoke(); + var awaiter = (SignalAwaiter)GCHandle.FromIntPtr(awaiterGCHandlePtr).Target; + + if (awaiter == null) + { + *r_awaiterIsNull = true; + return; + } + + *r_awaiterIsNull = false; + + awaiter._completed = true; + + object[] signalArgs = new object[argCount]; + + for (int i = 0; i < argCount; i++) + signalArgs[i] = Marshaling.variant_to_mono_object(args[i]); + + awaiter._result = signalArgs; + + awaiter._action?.Invoke(); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 6c3d673fdc..dfdef81f9e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1096,7 +1096,7 @@ namespace Godot using godot_string instanceStr = Marshaling.mono_string_to_godot(instance); using godot_string md5Text = default; NativeFuncs.godotsharp_string_md5_text(&instanceStr, &md5Text); - return Marshaling.mono_string_from_godot(&md5Text); + return Marshaling.mono_string_from_godot(md5Text); } /// <summary> @@ -1345,7 +1345,7 @@ namespace Godot using godot_string instanceStr = Marshaling.mono_string_to_godot(instance); using godot_string sha256Text = default; NativeFuncs.godotsharp_string_sha256_text(&instanceStr, &sha256Text); - return Marshaling.mono_string_from_godot(&sha256Text); + return Marshaling.mono_string_from_godot(sha256Text); } /// <summary> @@ -1401,7 +1401,7 @@ namespace Godot using godot_string instanceStr = Marshaling.mono_string_to_godot(instance); using godot_string simplifiedPath = default; NativeFuncs.godotsharp_string_simplify_path(&instanceStr, &simplifiedPath); - return Marshaling.mono_string_from_godot(&simplifiedPath); + return Marshaling.mono_string_from_godot(simplifiedPath); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs index 40d282eab4..84b0ab623c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs @@ -13,7 +13,7 @@ namespace Godot /// </summary> public sealed class StringName : IDisposable { - internal godot_string_name NativeValue; + public godot_string_name NativeValue; ~StringName() { @@ -86,7 +86,7 @@ namespace Godot godot_string_name src = NativeValue; NativeFuncs.godotsharp_string_name_as_string(&dest, &src); using (dest) - return Marshaling.mono_string_from_godot(&dest); + return Marshaling.mono_string_from_godot(dest); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 36faf92144..6a529de99b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -32,6 +32,9 @@ <Compile Include="Core\Attributes\SignalAttribute.cs" /> <Compile Include="Core\Attributes\ToolAttribute.cs" /> <Compile Include="Core\Basis.cs" /> + <Compile Include="Core\Bridge\CSharpInstanceBridge.cs" /> + <Compile Include="Core\Bridge\GCHandleBridge.cs" /> + <Compile Include="Core\Bridge\ScriptManagerBridge.cs" /> <Compile Include="Core\Callable.cs" /> <Compile Include="Core\Color.cs" /> <Compile Include="Core\Colors.cs" /> @@ -71,7 +74,6 @@ <Compile Include="Core\NativeInterop\NativeFuncs.cs" /> <Compile Include="Core\NativeInterop\InteropStructs.cs" /> <Compile Include="Core\NativeInterop\Marshaling.cs" /> - <Compile Include="Core\ScriptManager.cs" /> <Compile Include="Core\SignalInfo.cs" /> <Compile Include="Core\SignalAwaiter.cs" /> <Compile Include="Core\StringExtensions.cs" /> |