From f66a352c0fc03758e3ac54443a70ded6afe22d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Rold=C3=A1n=20Etcheverry?= Date: Fri, 28 Oct 2022 22:59:15 +0200 Subject: C#: Add Variant conversion callbacks for generic Godot collections This allows using generic Godot collections as type arguments for other generic Godot collections. This also allows generic Godot collections as parameter or return type in dynamic Callable invocations. --- .../mono/glue/GodotSharp/GodotSharp/Core/Array.cs | 32 +++++++---- .../glue/GodotSharp/GodotSharp/Core/Dictionary.cs | 67 +++++++++++++--------- .../NativeInterop/VariantConversionCallbacks.cs | 44 ++++++++++++++ 3 files changed, 106 insertions(+), 37 deletions(-) (limited to 'modules/mono/glue') diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index 1c98dfcdf6..f1b46e293b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -489,25 +489,37 @@ namespace Godot.Collections ICollection, IEnumerable { + private static godot_variant ToVariantFunc(in Array godotArray) => + VariantUtils.CreateFromArray(godotArray); + + private static Array FromVariantFunc(in godot_variant variant) => + VariantUtils.ConvertToArrayObject(variant); + // ReSharper disable StaticMemberInGenericType // Warning is about unique static fields being created for each generic type combination: // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html // In our case this is exactly what we want. - private static unsafe delegate* managed _convertToVariantCallback; - private static unsafe delegate* managed _convertToManagedCallback; + private static readonly unsafe delegate* managed ConvertToVariantCallback; + private static readonly unsafe delegate* managed ConvertToManagedCallback; // ReSharper restore StaticMemberInGenericType static unsafe Array() { - _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); - _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); + VariantConversionCallbacks.GenericConversionCallbacks[typeof(Array)] = + ( + (IntPtr)(delegate* managed, godot_variant>)&ToVariantFunc, + (IntPtr)(delegate* managed>)&FromVariantFunc + ); + + ConvertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); + ConvertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); } private static unsafe void ValidateVariantConversionCallbacks() { - if (_convertToVariantCallback == null || _convertToManagedCallback == null) + if (ConvertToVariantCallback == null || ConvertToManagedCallback == null) { throw new InvalidOperationException( $"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'."); @@ -653,7 +665,7 @@ namespace Godot.Collections get { _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); - return _convertToManagedCallback(borrowElem); + return ConvertToManagedCallback(borrowElem); } set { @@ -663,7 +675,7 @@ namespace Godot.Collections godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); godot_variant* itemPtr = &ptrw[index]; (*itemPtr).Dispose(); - *itemPtr = _convertToVariantCallback(value); + *itemPtr = ConvertToVariantCallback(value); } } @@ -675,7 +687,7 @@ namespace Godot.Collections /// The index of the item, or -1 if not found. public unsafe int IndexOf(T item) { - using var variantValue = _convertToVariantCallback(item); + using var variantValue = ConvertToVariantCallback(item); var self = (godot_array)_underlyingArray.NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } @@ -693,7 +705,7 @@ namespace Godot.Collections if (index < 0 || index > Count) throw new ArgumentOutOfRangeException(nameof(index)); - using var variantValue = _convertToVariantCallback(item); + using var variantValue = ConvertToVariantCallback(item); var self = (godot_array)_underlyingArray.NativeValue; NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); } @@ -726,7 +738,7 @@ namespace Godot.Collections /// The new size after adding the item. public unsafe void Add(T item) { - using var variantValue = _convertToVariantCallback(item); + using var variantValue = ConvertToVariantCallback(item); var self = (godot_array)_underlyingArray.NativeValue; _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index 93103d0f6b..f8793332a0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -356,35 +356,47 @@ namespace Godot.Collections IDictionary, IReadOnlyDictionary { + private static godot_variant ToVariantFunc(in Dictionary godotDictionary) => + VariantUtils.CreateFromDictionary(godotDictionary); + + private static Dictionary FromVariantFunc(in godot_variant variant) => + VariantUtils.ConvertToDictionaryObject(variant); + // ReSharper disable StaticMemberInGenericType // Warning is about unique static fields being created for each generic type combination: // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html // In our case this is exactly what we want. - private static unsafe delegate* managed _convertKeyToVariantCallback; - private static unsafe delegate* managed _convertKeyToManagedCallback; - private static unsafe delegate* managed _convertValueToVariantCallback; - private static unsafe delegate* managed _convertValueToManagedCallback; + private static readonly unsafe delegate* managed ConvertKeyToVariantCallback; + private static readonly unsafe delegate* managed ConvertKeyToManagedCallback; + private static readonly unsafe delegate* managed ConvertValueToVariantCallback; + private static readonly unsafe delegate* managed ConvertValueToManagedCallback; // ReSharper restore StaticMemberInGenericType static unsafe Dictionary() { - _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); - _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); - _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); - _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); + VariantConversionCallbacks.GenericConversionCallbacks[typeof(Dictionary)] = + ( + (IntPtr)(delegate* managed, godot_variant>)&ToVariantFunc, + (IntPtr)(delegate* managed>)&FromVariantFunc + ); + + ConvertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); + ConvertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); + ConvertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); + ConvertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); } private static unsafe void ValidateVariantConversionCallbacks() { - if (_convertKeyToVariantCallback == null || _convertKeyToManagedCallback == null) + if (ConvertKeyToVariantCallback == null || ConvertKeyToManagedCallback == null) { throw new InvalidOperationException( $"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'."); } - if (_convertValueToVariantCallback == null || _convertValueToManagedCallback == null) + if (ConvertValueToVariantCallback == null || ConvertValueToManagedCallback == null) { throw new InvalidOperationException( $"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'."); @@ -473,14 +485,14 @@ namespace Godot.Collections { get { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant value).ToBool()) { using (value) - return _convertValueToManagedCallback(value); + return ConvertValueToManagedCallback(value); } else { @@ -489,8 +501,8 @@ namespace Godot.Collections } set { - using var variantKey = _convertKeyToVariantCallback(key); - using var variantValue = _convertValueToVariantCallback(value); + using var variantKey = ConvertKeyToVariantCallback(key); + using var variantValue = ConvertValueToVariantCallback(value); var self = (godot_dictionary)_underlyingDict.NativeValue; NativeFuncs.godotsharp_dictionary_set_value(ref self, variantKey, variantValue); @@ -539,8 +551,8 @@ namespace Godot.Collections using (value) { return new KeyValuePair( - _convertKeyToManagedCallback(key), - _convertValueToManagedCallback(value)); + ConvertKeyToManagedCallback(key), + ConvertValueToManagedCallback(value)); } } @@ -552,13 +564,13 @@ namespace Godot.Collections /// The object to add. public unsafe void Add(TKey key, TValue value) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) throw new ArgumentException("An element with the same key already exists.", nameof(key)); - using var variantValue = _convertValueToVariantCallback(value); + using var variantValue = ConvertValueToVariantCallback(value); NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); } @@ -569,7 +581,7 @@ namespace Godot.Collections /// Whether or not this dictionary contains the given key. public unsafe bool ContainsKey(TKey key) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool(); } @@ -580,7 +592,7 @@ namespace Godot.Collections /// The key of the element to remove. public unsafe bool Remove(TKey key) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); } @@ -593,13 +605,13 @@ namespace Godot.Collections /// If an object was found for the given . public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); using (retValue) - value = found ? _convertValueToManagedCallback(retValue) : default; + value = found ? ConvertValueToManagedCallback(retValue) : default; return found; } @@ -625,7 +637,7 @@ namespace Godot.Collections unsafe bool ICollection>.Contains(KeyValuePair item) { - using var variantKey = _convertKeyToVariantCallback(item.Key); + using var variantKey = ConvertKeyToVariantCallback(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -635,7 +647,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = _convertValueToVariantCallback(item.Value); + using var variantValue = ConvertValueToVariantCallback(item.Value); return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); } } @@ -670,7 +682,7 @@ namespace Godot.Collections unsafe bool ICollection>.Remove(KeyValuePair item) { - using var variantKey = _convertKeyToVariantCallback(item.Key); + using var variantKey = ConvertKeyToVariantCallback(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -680,7 +692,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = _convertValueToVariantCallback(item.Value); + using var variantValue = ConvertValueToVariantCallback(item.Value); if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) { return NativeFuncs.godotsharp_dictionary_remove_key( @@ -717,6 +729,7 @@ namespace Godot.Collections public static implicit operator Variant(Dictionary from) => Variant.CreateFrom(from); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Dictionary(Variant from) => from.AsGodotDictionary(); + public static explicit operator Dictionary(Variant from) => + from.AsGodotDictionary(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs index 2707cb9bd2..4b3db0c01a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs @@ -1,11 +1,15 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace Godot.NativeInterop; // TODO: Change VariantConversionCallbacks. Store the callback in a static field for quick repeated access, instead of checking every time. internal static unsafe class VariantConversionCallbacks { + internal static System.Collections.Generic.Dictionary + GenericConversionCallbacks = new(); + [SuppressMessage("ReSharper", "RedundantNameQualifier")] internal static delegate* GetToVariantCallback() { @@ -503,6 +507,26 @@ internal static unsafe class VariantConversionCallbacks &FromVariant; } + // TODO: + // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. + // We could make the Godot collections implement an interface and use IsAssignableFrom instead. + // Or we could just skip the check and always look for a conversion callback for the type. + if (typeOfT.IsGenericType) + { + var genericTypeDef = typeOfT.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || + genericTypeDef == typeof(Godot.Collections.Array<>)) + { + RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); + + if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) + { + return (delegate*)genericConversion.ToVariant; + } + } + } + return null; } @@ -1006,6 +1030,26 @@ internal static unsafe class VariantConversionCallbacks &ToVariant; } + // TODO: + // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. + // We could make the Godot collections implement an interface and use IsAssignableFrom instead. + // Or we could just skip the check and always look for a conversion callback for the type. + if (typeOfT.IsGenericType) + { + var genericTypeDef = typeOfT.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || + genericTypeDef == typeof(Godot.Collections.Array<>)) + { + RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); + + if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) + { + return (delegate*)genericConversion.FromVariant; + } + } + } + // ReSharper restore RedundantCast return null; -- cgit v1.2.3