C#: Optimize Variant conversion callbacks

These callbacks are used for marshaling by callables and generic Godot
collections.

C# generics don't support specialization the way C++ templates do.
I knew NativeAOT could optimize away many type checks when the types
are known at compile time, but I didn't trust the JIT would do as good
a job, so I initially went with cached function pointers.

Well, it turns out the JIT is also very good at optimizing in this
scenario, so I'm changing the methods to do the conversion directly,
rather than returning a function pointer for the conversion.

The methods were moved to `VariantUtils`, and were renamed from
`GetFromVariantCallback/GetToVariantCallback` to `ConvertTo/CreateFrom`.

The new implementation looks like it goes through many `if` checks
at runtime to find the right branch for the type, but in practice it
works pretty much like template specialization. The JIT only generates
code for the relevant branch. Together with inlining, the result is
very close or the same as doing the conversion manually:

```cs
godot_variant variant;

int foo = variant.Int;
int bar = VariantUtils.ConvertTo<int>(variant);
```

If the type is a generic Godot collection, the conversion still goes
through a function pointer call.

The new code happens to be much shorter as well, with the file going
from 1057 lines to 407.

Side note: `Variant.cs` was mistakenly created in the wrong folder,
so I moved it to the `Core` folder.
This commit is contained in:
Ignacio Roldán Etcheverry 2022-11-06 01:27:55 +01:00
parent cdfef0c852
commit 3f645f980c
9 changed files with 556 additions and 1274 deletions

View file

@ -2274,7 +2274,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append(");\n");
// Generate Callable trampoline for the delegate
p_output << MEMBER_BEGIN "private static unsafe void " << p_isignal.proxy_name << "Trampoline"
p_output << MEMBER_BEGIN "private static void " << p_isignal.proxy_name << "Trampoline"
<< "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n"
<< INDENT1 "{\n"
<< INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n"
@ -2289,9 +2289,8 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output << ",";
}
// TODO: We don't need to use VariantConversionCallbacks. We have the type information so we can use [cs_variant_to_managed] and [cs_managed_to_variant].
p_output << "\n" INDENT3 "VariantConversionCallbacks.GetToManagedCallback<"
<< arg_type->cs_type << ">()(args[" << itos(idx) << "])";
p_output << sformat(arg_type->cs_variant_to_managed,
"args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name);
idx++;
}

View file

@ -495,35 +495,10 @@ namespace Godot.Collections
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 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()
{
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)
{
throw new InvalidOperationException(
$"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'.");
}
VariantUtils.GenericConversion<Array<T>>.ToVariantCb = &ToVariantFunc;
VariantUtils.GenericConversion<Array<T>>.FromVariantCb = &FromVariantFunc;
}
private readonly Array _underlyingArray;
@ -539,8 +514,6 @@ namespace Godot.Collections
/// </summary>
public Array()
{
ValidateVariantConversionCallbacks();
_underlyingArray = new Array();
}
@ -551,8 +524,6 @@ namespace Godot.Collections
/// <returns>A new Godot Array.</returns>
public Array(IEnumerable<T> collection)
{
ValidateVariantConversionCallbacks();
if (collection == null)
throw new ArgumentNullException(nameof(collection));
@ -569,8 +540,6 @@ namespace Godot.Collections
/// <returns>A new Godot Array.</returns>
public Array(T[] array) : this()
{
ValidateVariantConversionCallbacks();
if (array == null)
throw new ArgumentNullException(nameof(array));
@ -586,8 +555,6 @@ namespace Godot.Collections
/// <param name="array">The untyped array to construct from.</param>
public Array(Array array)
{
ValidateVariantConversionCallbacks();
_underlyingArray = array;
}
@ -665,7 +632,7 @@ namespace Godot.Collections
get
{
_underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem);
return ConvertToManagedCallback(borrowElem);
return VariantUtils.ConvertTo<T>(borrowElem);
}
set
{
@ -675,7 +642,7 @@ namespace Godot.Collections
godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
godot_variant* itemPtr = &ptrw[index];
(*itemPtr).Dispose();
*itemPtr = ConvertToVariantCallback(value);
*itemPtr = VariantUtils.CreateFrom(value);
}
}
@ -685,9 +652,9 @@ namespace Godot.Collections
/// </summary>
/// <param name="item">The item to search for.</param>
/// <returns>The index of the item, or -1 if not found.</returns>
public unsafe int IndexOf(T item)
public int IndexOf(T item)
{
using var variantValue = ConvertToVariantCallback(item);
using var variantValue = VariantUtils.CreateFrom(item);
var self = (godot_array)_underlyingArray.NativeValue;
return NativeFuncs.godotsharp_array_index_of(ref self, variantValue);
}
@ -700,12 +667,12 @@ namespace Godot.Collections
/// </summary>
/// <param name="index">The index to insert at.</param>
/// <param name="item">The item to insert.</param>
public unsafe void Insert(int index, T item)
public void Insert(int index, T item)
{
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index));
using var variantValue = ConvertToVariantCallback(item);
using var variantValue = VariantUtils.CreateFrom(item);
var self = (godot_array)_underlyingArray.NativeValue;
NativeFuncs.godotsharp_array_insert(ref self, index, variantValue);
}
@ -736,9 +703,9 @@ namespace Godot.Collections
/// </summary>
/// <param name="item">The item to add.</param>
/// <returns>The new size after adding the item.</returns>
public unsafe void Add(T item)
public void Add(T item)
{
using var variantValue = ConvertToVariantCallback(item);
using var variantValue = VariantUtils.CreateFrom(item);
var self = (godot_array)_underlyingArray.NativeValue;
_ = NativeFuncs.godotsharp_array_add(ref self, variantValue);
}

View file

@ -54,7 +54,7 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 1);
((Action<T0>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0])
VariantUtils.ConvertTo<T0>(args[0])
);
ret = default;
@ -73,8 +73,8 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 2);
((Action<T0, T1>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1])
);
ret = default;
@ -93,9 +93,9 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 3);
((Action<T0, T1, T2>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2])
);
ret = default;
@ -114,10 +114,10 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 4);
((Action<T0, T1, T2, T3>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3])
);
ret = default;
@ -136,11 +136,11 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 5);
((Action<T0, T1, T2, T3, T4>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4])
);
ret = default;
@ -159,12 +159,12 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 6);
((Action<T0, T1, T2, T3, T4, T5>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5])
);
ret = default;
@ -183,13 +183,13 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 7);
((Action<T0, T1, T2, T3, T4, T5, T6>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6])
);
ret = default;
@ -208,14 +208,14 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 8);
((Action<T0, T1, T2, T3, T4, T5, T6, T7>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7])
);
ret = default;
@ -234,15 +234,15 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 9);
((Action<T0, T1, T2, T3, T4, T5, T6, T7, T8>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]),
VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7]),
VariantUtils.ConvertTo<T8>(args[8])
);
ret = default;
@ -265,7 +265,7 @@ public readonly partial struct Callable
TResult res = ((Func<TResult>)delegateObj)();
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -281,10 +281,10 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 1);
TResult res = ((Func<T0, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0])
VariantUtils.ConvertTo<T0>(args[0])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -300,11 +300,11 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 2);
TResult res = ((Func<T0, T1, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -320,12 +320,12 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 3);
TResult res = ((Func<T0, T1, T2, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -341,13 +341,13 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 4);
TResult res = ((Func<T0, T1, T2, T3, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -363,14 +363,14 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 5);
TResult res = ((Func<T0, T1, T2, T3, T4, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -386,15 +386,15 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 6);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -410,16 +410,16 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 7);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -435,17 +435,17 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 8);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
@ -461,18 +461,18 @@ public readonly partial struct Callable
ThrowIfArgCountMismatch(args, 9);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]),
VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8])
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7]),
VariantUtils.ConvertTo<T8>(args[8])
);
ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);

View file

@ -362,45 +362,10 @@ namespace Godot.Collections
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 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()
{
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)
{
throw new InvalidOperationException(
$"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'.");
}
if (ConvertValueToVariantCallback == null || ConvertValueToManagedCallback == null)
{
throw new InvalidOperationException(
$"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'.");
}
VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.ToVariantCb = &ToVariantFunc;
VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.FromVariantCb = &FromVariantFunc;
}
private readonly Dictionary _underlyingDict;
@ -416,8 +381,6 @@ namespace Godot.Collections
/// </summary>
public Dictionary()
{
ValidateVariantConversionCallbacks();
_underlyingDict = new Dictionary();
}
@ -428,8 +391,6 @@ namespace Godot.Collections
/// <returns>A new Godot Dictionary.</returns>
public Dictionary(IDictionary<TKey, TValue> dictionary)
{
ValidateVariantConversionCallbacks();
if (dictionary == null)
throw new ArgumentNullException(nameof(dictionary));
@ -446,8 +407,6 @@ namespace Godot.Collections
/// <returns>A new Godot Dictionary.</returns>
public Dictionary(Dictionary dictionary)
{
ValidateVariantConversionCallbacks();
_underlyingDict = dictionary;
}
@ -481,18 +440,18 @@ namespace Godot.Collections
/// Returns the value at the given <paramref name="key"/>.
/// </summary>
/// <value>The value at the given <paramref name="key"/>.</value>
public unsafe TValue this[TKey key]
public TValue this[TKey key]
{
get
{
using var variantKey = ConvertKeyToVariantCallback(key);
using var variantKey = VariantUtils.CreateFrom(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 VariantUtils.ConvertTo<TValue>(value);
}
else
{
@ -501,8 +460,8 @@ namespace Godot.Collections
}
set
{
using var variantKey = ConvertKeyToVariantCallback(key);
using var variantValue = ConvertValueToVariantCallback(value);
using var variantKey = VariantUtils.CreateFrom(key);
using var variantValue = VariantUtils.CreateFrom(value);
var self = (godot_dictionary)_underlyingDict.NativeValue;
NativeFuncs.godotsharp_dictionary_set_value(ref self,
variantKey, variantValue);
@ -541,7 +500,7 @@ namespace Godot.Collections
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
private unsafe KeyValuePair<TKey, TValue> GetKeyValuePair(int index)
private KeyValuePair<TKey, TValue> GetKeyValuePair(int index)
{
var self = (godot_dictionary)_underlyingDict.NativeValue;
NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index,
@ -551,8 +510,8 @@ namespace Godot.Collections
using (value)
{
return new KeyValuePair<TKey, TValue>(
ConvertKeyToManagedCallback(key),
ConvertValueToManagedCallback(value));
VariantUtils.ConvertTo<TKey>(key),
VariantUtils.ConvertTo<TValue>(value));
}
}
@ -562,15 +521,15 @@ namespace Godot.Collections
/// </summary>
/// <param name="key">The key at which to add the object.</param>
/// <param name="value">The object to add.</param>
public unsafe void Add(TKey key, TValue value)
public void Add(TKey key, TValue value)
{
using var variantKey = ConvertKeyToVariantCallback(key);
using var variantKey = VariantUtils.CreateFrom(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 = VariantUtils.CreateFrom(value);
NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue);
}
@ -579,9 +538,9 @@ namespace Godot.Collections
/// </summary>
/// <param name="key">The key to look for.</param>
/// <returns>Whether or not this dictionary contains the given key.</returns>
public unsafe bool ContainsKey(TKey key)
public bool ContainsKey(TKey key)
{
using var variantKey = ConvertKeyToVariantCallback(key);
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool();
}
@ -590,9 +549,9 @@ namespace Godot.Collections
/// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key.
/// </summary>
/// <param name="key">The key of the element to remove.</param>
public unsafe bool Remove(TKey key)
public bool Remove(TKey key)
{
using var variantKey = ConvertKeyToVariantCallback(key);
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool();
}
@ -603,15 +562,15 @@ namespace Godot.Collections
/// <param name="key">The key of the element to get.</param>
/// <param name="value">The value at the given <paramref name="key"/>.</param>
/// <returns>If an object was found for the given <paramref name="key"/>.</returns>
public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
using var variantKey = ConvertKeyToVariantCallback(key);
using var variantKey = VariantUtils.CreateFrom(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 ? VariantUtils.ConvertTo<TValue>(retValue) : default;
return found;
}
@ -635,9 +594,9 @@ namespace Godot.Collections
/// </summary>
public void Clear() => _underlyingDict.Clear();
unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
using var variantKey = ConvertKeyToVariantCallback(item.Key);
using var variantKey = VariantUtils.CreateFrom(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
@ -647,7 +606,7 @@ namespace Godot.Collections
if (!found)
return false;
using var variantValue = ConvertValueToVariantCallback(item.Value);
using var variantValue = VariantUtils.CreateFrom(item.Value);
return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool();
}
}
@ -680,9 +639,9 @@ namespace Godot.Collections
}
}
unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
using var variantKey = ConvertKeyToVariantCallback(item.Key);
using var variantKey = VariantUtils.CreateFrom(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
@ -692,7 +651,7 @@ namespace Godot.Collections
if (!found)
return false;
using var variantValue = ConvertValueToVariantCallback(item.Value);
using var variantValue = VariantUtils.CreateFrom(item.Value);
if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool())
{
return NativeFuncs.godotsharp_dictionary_remove_key(

View file

@ -8,7 +8,7 @@ using Godot.Collections;
namespace Godot.NativeInterop
{
public static class VariantUtils
public static partial class VariantUtils
{
public static godot_variant CreateFromRID(RID from)
=> new() { Type = Variant.Type.Rid, RID = from };

View file

@ -0,0 +1,406 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Godot.NativeInterop;
public partial class VariantUtils
{
private static Exception UnsupportedType<T>() => throw new InvalidOperationException(
$"The type is not supported for conversion to/from Variant: '{typeof(T).FullName}'");
internal static class GenericConversion<T>
{
public static unsafe godot_variant ToVariant(in T from) =>
ToVariantCb != null ? ToVariantCb(from) : throw UnsupportedType<T>();
public static unsafe T FromVariant(in godot_variant variant) =>
FromVariantCb != null ? FromVariantCb(variant) : throw UnsupportedType<T>();
// ReSharper disable once StaticMemberInGenericType
internal static unsafe delegate*<in T, godot_variant> ToVariantCb;
// ReSharper disable once StaticMemberInGenericType
internal static unsafe delegate*<in godot_variant, T> FromVariantCb;
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
static GenericConversion()
{
RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
public static godot_variant CreateFrom<[MustBeVariant] T>(in T from)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static TTo UnsafeAs<TTo>(in T f) => Unsafe.As<T, TTo>(ref Unsafe.AsRef(f));
// `typeof(T) == typeof(X)` is optimized away. We cannot cache `typeof(T)` in a local variable, as it's not optimized when done like that.
if (typeof(T) == typeof(bool))
return CreateFromBool(UnsafeAs<bool>(from));
if (typeof(T) == typeof(char))
return CreateFromInt(UnsafeAs<char>(from));
if (typeof(T) == typeof(sbyte))
return CreateFromInt(UnsafeAs<sbyte>(from));
if (typeof(T) == typeof(short))
return CreateFromInt(UnsafeAs<short>(from));
if (typeof(T) == typeof(int))
return CreateFromInt(UnsafeAs<int>(from));
if (typeof(T) == typeof(long))
return CreateFromInt(UnsafeAs<long>(from));
if (typeof(T) == typeof(byte))
return CreateFromInt(UnsafeAs<byte>(from));
if (typeof(T) == typeof(ushort))
return CreateFromInt(UnsafeAs<ushort>(from));
if (typeof(T) == typeof(uint))
return CreateFromInt(UnsafeAs<uint>(from));
if (typeof(T) == typeof(ulong))
return CreateFromInt(UnsafeAs<ulong>(from));
if (typeof(T) == typeof(float))
return CreateFromFloat(UnsafeAs<float>(from));
if (typeof(T) == typeof(double))
return CreateFromFloat(UnsafeAs<double>(from));
if (typeof(T) == typeof(Vector2))
return CreateFromVector2(UnsafeAs<Vector2>(from));
if (typeof(T) == typeof(Vector2i))
return CreateFromVector2i(UnsafeAs<Vector2i>(from));
if (typeof(T) == typeof(Rect2))
return CreateFromRect2(UnsafeAs<Rect2>(from));
if (typeof(T) == typeof(Rect2i))
return CreateFromRect2i(UnsafeAs<Rect2i>(from));
if (typeof(T) == typeof(Transform2D))
return CreateFromTransform2D(UnsafeAs<Transform2D>(from));
if (typeof(T) == typeof(Vector3))
return CreateFromVector3(UnsafeAs<Vector3>(from));
if (typeof(T) == typeof(Vector3i))
return CreateFromVector3i(UnsafeAs<Vector3i>(from));
if (typeof(T) == typeof(Basis))
return CreateFromBasis(UnsafeAs<Basis>(from));
if (typeof(T) == typeof(Quaternion))
return CreateFromQuaternion(UnsafeAs<Quaternion>(from));
if (typeof(T) == typeof(Transform3D))
return CreateFromTransform3D(UnsafeAs<Transform3D>(from));
if (typeof(T) == typeof(Vector4))
return CreateFromVector4(UnsafeAs<Vector4>(from));
if (typeof(T) == typeof(Vector4i))
return CreateFromVector4i(UnsafeAs<Vector4i>(from));
if (typeof(T) == typeof(AABB))
return CreateFromAABB(UnsafeAs<AABB>(from));
if (typeof(T) == typeof(Color))
return CreateFromColor(UnsafeAs<Color>(from));
if (typeof(T) == typeof(Plane))
return CreateFromPlane(UnsafeAs<Plane>(from));
if (typeof(T) == typeof(Callable))
return CreateFromCallable(UnsafeAs<Callable>(from));
if (typeof(T) == typeof(SignalInfo))
return CreateFromSignalInfo(UnsafeAs<SignalInfo>(from));
if (typeof(T) == typeof(string))
return CreateFromString(UnsafeAs<string>(from));
if (typeof(T) == typeof(byte[]))
return CreateFromPackedByteArray(UnsafeAs<byte[]>(from));
if (typeof(T) == typeof(int[]))
return CreateFromPackedInt32Array(UnsafeAs<int[]>(from));
if (typeof(T) == typeof(long[]))
return CreateFromPackedInt64Array(UnsafeAs<long[]>(from));
if (typeof(T) == typeof(float[]))
return CreateFromPackedFloat32Array(UnsafeAs<float[]>(from));
if (typeof(T) == typeof(double[]))
return CreateFromPackedFloat64Array(UnsafeAs<double[]>(from));
if (typeof(T) == typeof(string[]))
return CreateFromPackedStringArray(UnsafeAs<string[]>(from));
if (typeof(T) == typeof(Vector2[]))
return CreateFromPackedVector2Array(UnsafeAs<Vector2[]>(from));
if (typeof(T) == typeof(Vector3[]))
return CreateFromPackedVector3Array(UnsafeAs<Vector3[]>(from));
if (typeof(T) == typeof(Color[]))
return CreateFromPackedColorArray(UnsafeAs<Color[]>(from));
if (typeof(T) == typeof(StringName[]))
return CreateFromSystemArrayOfStringName(UnsafeAs<StringName[]>(from));
if (typeof(T) == typeof(NodePath[]))
return CreateFromSystemArrayOfNodePath(UnsafeAs<NodePath[]>(from));
if (typeof(T) == typeof(RID[]))
return CreateFromSystemArrayOfRID(UnsafeAs<RID[]>(from));
if (typeof(T) == typeof(StringName))
return CreateFromStringName(UnsafeAs<StringName>(from));
if (typeof(T) == typeof(NodePath))
return CreateFromNodePath(UnsafeAs<NodePath>(from));
if (typeof(T) == typeof(RID))
return CreateFromRID(UnsafeAs<RID>(from));
if (typeof(T) == typeof(Godot.Collections.Dictionary))
return CreateFromDictionary(UnsafeAs<Godot.Collections.Dictionary>(from));
if (typeof(T) == typeof(Godot.Collections.Array))
return CreateFromArray(UnsafeAs<Godot.Collections.Array>(from));
if (typeof(T) == typeof(Variant))
return NativeFuncs.godotsharp_variant_new_copy((godot_variant)UnsafeAs<Variant>(from).NativeVar);
// More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away.
// `typeof(X).IsAssignableFrom(typeof(T))` is optimized away
if (typeof(Godot.Object).IsAssignableFrom(typeof(T)))
return CreateFromGodotObject(UnsafeAs<Godot.Object>(from));
// `typeof(T).IsValueType` is optimized away
// `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113
// Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job!
if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T)))
{
// `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away.
// Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away.
// We don't need to know whether it's signed or unsigned.
if (Unsafe.SizeOf<T>() == 1)
return CreateFromInt(UnsafeAs<sbyte>(from));
if (Unsafe.SizeOf<T>() == 2)
return CreateFromInt(UnsafeAs<short>(from));
if (Unsafe.SizeOf<T>() == 4)
return CreateFromInt(UnsafeAs<int>(from));
if (Unsafe.SizeOf<T>() == 8)
return CreateFromInt(UnsafeAs<long>(from));
throw UnsupportedType<T>();
}
return GenericConversion<T>.ToVariant(from);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
public static T ConvertTo<[MustBeVariant] T>(in godot_variant variant)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static T UnsafeAsT<TFrom>(TFrom f) => Unsafe.As<TFrom, T>(ref Unsafe.AsRef(f));
if (typeof(T) == typeof(bool))
return UnsafeAsT(ConvertToBool(variant));
if (typeof(T) == typeof(char))
return UnsafeAsT(ConvertToChar(variant));
if (typeof(T) == typeof(sbyte))
return UnsafeAsT(ConvertToInt8(variant));
if (typeof(T) == typeof(short))
return UnsafeAsT(ConvertToInt16(variant));
if (typeof(T) == typeof(int))
return UnsafeAsT(ConvertToInt32(variant));
if (typeof(T) == typeof(long))
return UnsafeAsT(ConvertToInt64(variant));
if (typeof(T) == typeof(byte))
return UnsafeAsT(ConvertToUInt8(variant));
if (typeof(T) == typeof(ushort))
return UnsafeAsT(ConvertToUInt16(variant));
if (typeof(T) == typeof(uint))
return UnsafeAsT(ConvertToUInt32(variant));
if (typeof(T) == typeof(ulong))
return UnsafeAsT(ConvertToUInt64(variant));
if (typeof(T) == typeof(float))
return UnsafeAsT(ConvertToFloat32(variant));
if (typeof(T) == typeof(double))
return UnsafeAsT(ConvertToFloat64(variant));
if (typeof(T) == typeof(Vector2))
return UnsafeAsT(ConvertToVector2(variant));
if (typeof(T) == typeof(Vector2i))
return UnsafeAsT(ConvertToVector2i(variant));
if (typeof(T) == typeof(Rect2))
return UnsafeAsT(ConvertToRect2(variant));
if (typeof(T) == typeof(Rect2i))
return UnsafeAsT(ConvertToRect2i(variant));
if (typeof(T) == typeof(Transform2D))
return UnsafeAsT(ConvertToTransform2D(variant));
if (typeof(T) == typeof(Vector3))
return UnsafeAsT(ConvertToVector3(variant));
if (typeof(T) == typeof(Vector3i))
return UnsafeAsT(ConvertToVector3i(variant));
if (typeof(T) == typeof(Basis))
return UnsafeAsT(ConvertToBasis(variant));
if (typeof(T) == typeof(Quaternion))
return UnsafeAsT(ConvertToQuaternion(variant));
if (typeof(T) == typeof(Transform3D))
return UnsafeAsT(ConvertToTransform3D(variant));
if (typeof(T) == typeof(Vector4))
return UnsafeAsT(ConvertToVector4(variant));
if (typeof(T) == typeof(Vector4i))
return UnsafeAsT(ConvertToVector4i(variant));
if (typeof(T) == typeof(AABB))
return UnsafeAsT(ConvertToAABB(variant));
if (typeof(T) == typeof(Color))
return UnsafeAsT(ConvertToColor(variant));
if (typeof(T) == typeof(Plane))
return UnsafeAsT(ConvertToPlane(variant));
if (typeof(T) == typeof(Callable))
return UnsafeAsT(ConvertToCallableManaged(variant));
if (typeof(T) == typeof(SignalInfo))
return UnsafeAsT(ConvertToSignalInfo(variant));
if (typeof(T) == typeof(string))
return UnsafeAsT(ConvertToStringObject(variant));
if (typeof(T) == typeof(byte[]))
return UnsafeAsT(ConvertAsPackedByteArrayToSystemArray(variant));
if (typeof(T) == typeof(int[]))
return UnsafeAsT(ConvertAsPackedInt32ArrayToSystemArray(variant));
if (typeof(T) == typeof(long[]))
return UnsafeAsT(ConvertAsPackedInt64ArrayToSystemArray(variant));
if (typeof(T) == typeof(float[]))
return UnsafeAsT(ConvertAsPackedFloat32ArrayToSystemArray(variant));
if (typeof(T) == typeof(double[]))
return UnsafeAsT(ConvertAsPackedFloat64ArrayToSystemArray(variant));
if (typeof(T) == typeof(string[]))
return UnsafeAsT(ConvertAsPackedStringArrayToSystemArray(variant));
if (typeof(T) == typeof(Vector2[]))
return UnsafeAsT(ConvertAsPackedVector2ArrayToSystemArray(variant));
if (typeof(T) == typeof(Vector3[]))
return UnsafeAsT(ConvertAsPackedVector3ArrayToSystemArray(variant));
if (typeof(T) == typeof(Color[]))
return UnsafeAsT(ConvertAsPackedColorArrayToSystemArray(variant));
if (typeof(T) == typeof(StringName[]))
return UnsafeAsT(ConvertToSystemArrayOfStringName(variant));
if (typeof(T) == typeof(NodePath[]))
return UnsafeAsT(ConvertToSystemArrayOfNodePath(variant));
if (typeof(T) == typeof(RID[]))
return UnsafeAsT(ConvertToSystemArrayOfRID(variant));
if (typeof(T) == typeof(StringName))
return UnsafeAsT(ConvertToStringNameObject(variant));
if (typeof(T) == typeof(NodePath))
return UnsafeAsT(ConvertToNodePathObject(variant));
if (typeof(T) == typeof(RID))
return UnsafeAsT(ConvertToRID(variant));
if (typeof(T) == typeof(Godot.Collections.Dictionary))
return UnsafeAsT(ConvertToDictionaryObject(variant));
if (typeof(T) == typeof(Godot.Collections.Array))
return UnsafeAsT(ConvertToArrayObject(variant));
if (typeof(T) == typeof(Variant))
return UnsafeAsT(Variant.CreateCopyingBorrowed(variant));
// More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away.
// `typeof(X).IsAssignableFrom(typeof(T))` is optimized away
if (typeof(Godot.Object).IsAssignableFrom(typeof(T)))
return (T)(object)ConvertToGodotObject(variant);
// `typeof(T).IsValueType` is optimized away
// `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113
// Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job!
if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T)))
{
// `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away.
// Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away.
// We don't need to know whether it's signed or unsigned.
if (Unsafe.SizeOf<T>() == 1)
return UnsafeAsT(ConvertToInt8(variant));
if (Unsafe.SizeOf<T>() == 2)
return UnsafeAsT(ConvertToInt16(variant));
if (Unsafe.SizeOf<T>() == 4)
return UnsafeAsT(ConvertToInt32(variant));
if (Unsafe.SizeOf<T>() == 8)
return UnsafeAsT(ConvertToInt64(variant));
throw UnsupportedType<T>();
}
return GenericConversion<T>.FromVariant(variant);
}
}

View file

@ -120,6 +120,14 @@ public partial struct Variant : IDisposable
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant From<[MustBeVariant] T>(in T from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFrom(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T As<[MustBeVariant] T>() =>
VariantUtils.ConvertTo<T>(NativeVar.DangerousSelfRef);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AsBool() =>
VariantUtils.ConvertToBool((godot_variant)NativeVar);

View file

@ -101,9 +101,9 @@
<Compile Include="Core\NativeInterop\InteropUtils.cs" />
<Compile Include="Core\NativeInterop\NativeFuncs.extended.cs" />
<Compile Include="Core\NativeInterop\NativeVariantPtrArgs.cs" />
<Compile Include="Core\NativeInterop\VariantConversionCallbacks.cs" />
<Compile Include="Core\NativeInterop\VariantSpanHelpers.cs" />
<Compile Include="Core\NativeInterop\VariantUtils.cs" />
<Compile Include="Core\NativeInterop\VariantUtils.generic.cs" />
<Compile Include="Core\NodePath.cs" />
<Compile Include="Core\Object.base.cs" />
<Compile Include="Core\Object.exceptions.cs" />
@ -123,6 +123,7 @@
<Compile Include="Core\StringName.cs" />
<Compile Include="Core\Transform2D.cs" />
<Compile Include="Core\Transform3D.cs" />
<Compile Include="Core\Variant.cs" />
<Compile Include="Core\Vector2.cs" />
<Compile Include="Core\Vector2i.cs" />
<Compile Include="Core\Vector3.cs" />
@ -131,7 +132,6 @@
<Compile Include="Core\Vector4i.cs" />
<Compile Include="GlobalUsings.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Variant.cs" />
</ItemGroup>
<!--
We import a props file with auto-generated includes. This works well with Rider.