mirror of
https://github.com/godotengine/godot.git
synced 2024-11-10 22:23:07 +00:00
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.
This commit is contained in:
parent
282bd37e5c
commit
f66a352c0f
@ -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);
|
||||
}
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user