diff options
3 files changed, 106 insertions, 37 deletions
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<T>, IEnumerable<T> { + private static godot_variant ToVariantFunc(in Array<T> godotArray) => + VariantUtils.CreateFromArray(godotArray); + + private static Array<T> FromVariantFunc(in godot_variant variant) => + VariantUtils.ConvertToArrayObject<T>(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<in T, godot_variant> _convertToVariantCallback; - private static unsafe delegate* managed<in godot_variant, T> _convertToManagedCallback; + private static readonly unsafe delegate* managed<in T, godot_variant> ConvertToVariantCallback; + private static readonly unsafe delegate* managed<in godot_variant, T> ConvertToManagedCallback; // ReSharper restore StaticMemberInGenericType static unsafe Array() { - _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); - _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); + VariantConversionCallbacks.GenericConversionCallbacks[typeof(Array<T>)] = + ( + (IntPtr)(delegate* managed<in Array<T>, godot_variant>)&ToVariantFunc, + (IntPtr)(delegate* managed<in godot_variant, Array<T>>)&FromVariantFunc + ); + + ConvertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); + ConvertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); } 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 /// <returns>The index of the item, or -1 if not found.</returns> 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 /// <returns>The new size after adding the item.</returns> 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<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> { + private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) => + VariantUtils.CreateFromDictionary(godotDictionary); + + private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) => + VariantUtils.ConvertToDictionaryObject<TKey, TValue>(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<in TKey, godot_variant> _convertKeyToVariantCallback; - private static unsafe delegate* managed<in godot_variant, TKey> _convertKeyToManagedCallback; - private static unsafe delegate* managed<in TValue, godot_variant> _convertValueToVariantCallback; - private static unsafe delegate* managed<in godot_variant, TValue> _convertValueToManagedCallback; + private static readonly unsafe delegate* managed<in TKey, godot_variant> ConvertKeyToVariantCallback; + private static readonly unsafe delegate* managed<in godot_variant, TKey> ConvertKeyToManagedCallback; + private static readonly unsafe delegate* managed<in TValue, godot_variant> ConvertValueToVariantCallback; + private static readonly unsafe delegate* managed<in godot_variant, TValue> ConvertValueToManagedCallback; // ReSharper restore StaticMemberInGenericType static unsafe Dictionary() { - _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); - _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); - _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); - _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); + VariantConversionCallbacks.GenericConversionCallbacks[typeof(Dictionary<TKey, TValue>)] = + ( + (IntPtr)(delegate* managed<in Dictionary<TKey, TValue>, godot_variant>)&ToVariantFunc, + (IntPtr)(delegate* managed<in godot_variant, Dictionary<TKey, TValue>>)&FromVariantFunc + ); + + ConvertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); + ConvertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); + ConvertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); + ConvertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); } 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<TKey, TValue>( - _convertKeyToManagedCallback(key), - _convertValueToManagedCallback(value)); + ConvertKeyToManagedCallback(key), + ConvertValueToManagedCallback(value)); } } @@ -552,13 +564,13 @@ namespace Godot.Collections /// <param name="value">The object to add.</param> 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 /// <returns>Whether or not this dictionary contains the given key.</returns> 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 /// <param name="key">The key of the element to remove.</param> 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 /// <returns>If an object was found for the given <paramref name="key"/>.</returns> 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<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> 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<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> 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<TKey, TValue> from) => Variant.CreateFrom(from); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Dictionary<TKey, TValue>(Variant from) => from.AsGodotDictionary<TKey, TValue>(); + public static explicit operator Dictionary<TKey, TValue>(Variant from) => + from.AsGodotDictionary<TKey, TValue>(); } } 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<T>. 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<Type, (IntPtr ToVariant, IntPtr FromVariant)> + GenericConversionCallbacks = new(); + [SuppressMessage("ReSharper", "RedundantNameQualifier")] internal static delegate*<in T, godot_variant> GetToVariantCallback<T>() { @@ -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*<in T, godot_variant>)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*<in godot_variant, T>)genericConversion.FromVariant; + } + } + } + // ReSharper restore RedundantCast return null; |