From 0c30c678f0dde7a48f484f4ffdba24bb91243166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Rold=C3=A1n=20Etcheverry?= Date: Mon, 8 Aug 2022 01:53:54 +0200 Subject: [PATCH] C#: Re-introduce generic Godot Array and Dictionary This new version does not support the following type arguments: - Generic types - Array of Godot Object (Godot.Object[]) or derived types The new implementation uses delegate pointers to call the Variant conversion methods. We do type checking only once in the static constructor to get the conversion delegates. Now, we no longer need to do type checking every time, and we no longer have to box value types. This is the best implementation I could come up with, as C# generics don't support anything similar to C++ template specializations. --- .../ExportedFields.cs | 6 + .../ExportedProperties.cs | 6 + .../Godot.SourceGenerators/MarshalType.cs | 2 + .../Godot.SourceGenerators/MarshalUtils.cs | 38 +- .../GodotTools/Build/BuildOutputView.cs | 6 +- modules/mono/editor/bindings_generator.cpp | 15 - .../VisualShaderNodeCustom/basic.cs | 2 +- .../glue/GodotSharp/GodotSharp/Core/Array.cs | 361 ++++++- .../GodotSharp/GodotSharp/Core/Dictionary.cs | 378 +++++++ .../VariantConversionCallbacks.cs | 976 ++++++++++++++++++ .../Core/NativeInterop/VariantUtils.cs | 16 + .../GodotSharp/GodotSharp/GodotSharp.csproj | 1 + .../glue/GodotSharp/GodotSharp/Variant.cs | 16 + 13 files changed, 1792 insertions(+), 31 deletions(-) create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs index a5b4cb81f60..ac9f59aa99b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs @@ -99,5 +99,11 @@ namespace Godot.SourceGenerators.Sample [Export] private Godot.Collections.Array field_GodotArray = new() { "foo", 10, Vector2.Up, Colors.Chocolate }; + + [Export] private Godot.Collections.Dictionary field_GodotGenericDictionary = + new() { { "foo", true }, { "bar", false } }; + + [Export] private Godot.Collections.Array field_GodotGenericArray = + new() { 0, 1, 2, 3, 4, 5, 6 }; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs index eb35c88260b..4a0e8075f07 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs @@ -99,5 +99,11 @@ namespace Godot.SourceGenerators.Sample [Export] private Godot.Collections.Array property_GodotArray { get; set; } = new() { "foo", 10, Vector2.Up, Colors.Chocolate }; + + [Export] private Godot.Collections.Dictionary property_GodotGenericDictionary { get; set; } = + new() { { "foo", true }, { "bar", false } }; + + [Export] private Godot.Collections.Array property_GodotGenericArray { get; set; } = + new() { 0, 1, 2, 3, 4, 5, 6 }; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs index 3f767c8a5f7..15f5803bf07 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs @@ -67,5 +67,7 @@ namespace Godot.SourceGenerators RID, GodotDictionary, GodotArray, + GodotGenericDictionary, + GodotGenericArray, } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs index 43000377968..ca84518c0c2 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -78,6 +78,8 @@ namespace Godot.SourceGenerators MarshalType.RID => VariantType.Rid, MarshalType.GodotDictionary => VariantType.Dictionary, MarshalType.GodotArray => VariantType.Array, + MarshalType.GodotGenericDictionary => VariantType.Dictionary, + MarshalType.GodotGenericArray => VariantType.Array, _ => null }; @@ -214,13 +216,17 @@ namespace Godot.SourceGenerators _ => null }; case "Collections" - when !(type is INamedTypeSymbol { IsGenericType: true }) && - type.ContainingNamespace.FullQualifiedName() == - "Godot.Collections": + when type.ContainingNamespace.FullQualifiedName() == "Godot.Collections": return type switch { - { Name: "Dictionary" } => MarshalType.GodotDictionary, - { Name: "Array" } => MarshalType.GodotArray, + { Name: "Dictionary" } => + type is INamedTypeSymbol { IsGenericType: false } ? + MarshalType.GodotDictionary : + MarshalType.GodotGenericDictionary, + { Name: "Array" } => + type is INamedTypeSymbol { IsGenericType: false } ? + MarshalType.GodotArray : + MarshalType.GodotGenericArray, _ => null }; } @@ -283,6 +289,10 @@ namespace Godot.SourceGenerators string c, string d, string e, string f, string g) => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g); + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e, string f, string g, string h) + => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g).Append(h); + private const string VariantUtils = "global::Godot.NativeInterop.VariantUtils"; public static StringBuilder AppendNativeVariantToManagedExpr(this StringBuilder source, @@ -397,6 +407,13 @@ namespace Godot.SourceGenerators source.Append(VariantUtils, ".ConvertToDictionaryObject(", inputExpr, ")"), MarshalType.GodotArray => source.Append(VariantUtils, ".ConvertToArrayObject(", inputExpr, ")"), + MarshalType.GodotGenericDictionary => + source.Append(VariantUtils, ".ConvertToDictionaryObject<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">(", inputExpr, ")"), + MarshalType.GodotGenericArray => + source.Append(VariantUtils, ".ConvertToArrayObject<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">(", inputExpr, ")"), _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, "Received unexpected marshal type") }; @@ -511,6 +528,10 @@ namespace Godot.SourceGenerators source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), MarshalType.GodotArray => source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), + MarshalType.GodotGenericDictionary => + source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), + MarshalType.GodotGenericArray => + source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, "Received unexpected marshal type") }; @@ -576,6 +597,11 @@ namespace Godot.SourceGenerators MarshalType.RID => source.Append(inputExpr, ".AsRID()"), MarshalType.GodotDictionary => source.Append(inputExpr, ".AsGodotDictionary()"), MarshalType.GodotArray => source.Append(inputExpr, ".AsGodotArray()"), + MarshalType.GodotGenericDictionary => source.Append(inputExpr, ".AsGodotDictionary<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">()"), + MarshalType.GodotGenericArray => source.Append(inputExpr, ".AsGodotArray<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">()"), _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, "Received unexpected marshal type") }; @@ -636,6 +662,8 @@ namespace Godot.SourceGenerators case MarshalType.RID: case MarshalType.GodotDictionary: case MarshalType.GodotArray: + case MarshalType.GodotGenericDictionary: + case MarshalType.GodotGenericArray: return source.Append("Variant.CreateFrom(", inputExpr, ")"); case MarshalType.Enum: return source.Append("Variant.CreateFrom((long)", inputExpr, ")"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index a8128be9098..96d1fc28bfb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -58,7 +58,7 @@ namespace GodotTools.Build } // TODO Use List once we have proper serialization. - private Godot.Collections.Array _issues = new(); + private Godot.Collections.Array _issues = new(); private ItemList _issuesList; private PopupMenu _issuesListContextMenu; private TextEdit _buildLog; @@ -128,7 +128,7 @@ namespace GodotTools.Build if (issueIndex < 0 || issueIndex >= _issues.Count) throw new IndexOutOfRangeException("Issue index out of range"); - var issue = (BuildIssue)_issues[issueIndex]; + BuildIssue issue = _issues[issueIndex]; if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) return; @@ -162,7 +162,7 @@ namespace GodotTools.Build { for (int i = 0; i < _issues.Count; i++) { - var issue = (BuildIssue)_issues[i]; + BuildIssue issue = _issues[i]; if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) continue; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index b879c95fa17..73d8f230819 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2891,13 +2891,8 @@ bool BindingsGenerator::_populate_object_type_interfaces() { String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." + " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'."); } else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) { -// TODO: Enable once generic Array is re-implemented -#if 0 imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string)); -#else - imethod.return_type.cname = Variant::get_type_name(return_info.type); -#endif } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -2928,13 +2923,8 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.class_name != StringName()) { iarg.type.cname = arginfo.class_name; } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { -// TODO: Enable once generic Array is re-implemented -#if 0 iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); -#else - iarg.type.cname = Variant::get_type_name(arginfo.type); -#endif } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { @@ -3041,13 +3031,8 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.class_name != StringName()) { iarg.type.cname = arginfo.class_name; } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { -// TODO: Enable once generic Array is re-implemented -#if 0 iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); -#else - iarg.type.cname = Variant::get_type_name(arginfo.type); -#endif } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { diff --git a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs index a1b93e7daa3..bb482e0d6aa 100644 --- a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs +++ b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs @@ -55,7 +55,7 @@ public partial class VisualShaderNode_CLASS_ : _BASE_ return 0; } - public override string _GetCode(Godot.Collections.Array inputVars, Godot.Collections.Array outputVars, Shader.Mode mode, VisualShader.Type type) + public override string _GetCode(Godot.Collections.Array inputVars, Godot.Collections.Array outputVars, Shader.Mode mode, VisualShader.Type type) { return ""; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index 51d7d8195bd..81991c66264 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Godot.NativeInterop; @@ -424,13 +425,6 @@ namespace Godot.Collections } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal object GetAtAsType(int index, Type type) - { - GetVariantBorrowElementAt(index, out godot_variant borrowElem); - return Marshaling.ConvertVariantToManagedObjectOfType(borrowElem, type); - } - // IEnumerable /// @@ -479,4 +473,357 @@ namespace Godot.Collections elem = NativeValue.DangerousSelfRef.Elements[index]; } } + + /// + /// Typed wrapper around Godot's Array class, an array of Variant + /// typed elements allocated in the engine in C++. Useful when + /// interfacing with the engine. Otherwise prefer .NET collections + /// such as arrays or . + /// + /// The type of the array. + [SuppressMessage("ReSharper", "RedundantExtendsListEntry")] + [SuppressMessage("Naming", "CA1710", MessageId = "Identifiers should have correct suffix")] + public sealed class Array : + IList, + IReadOnlyList, + ICollection, + IEnumerable + { + // 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; + + // ReSharper restore StaticMemberInGenericType + + static unsafe Array() + { + _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); + _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); + } + + 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}'"); + } + } + + private readonly Array _underlyingArray; + + internal ref godot_array.movable NativeValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _underlyingArray.NativeValue; + } + + /// + /// Constructs a new empty . + /// + public Array() + { + ValidateVariantConversionCallbacks(); + + _underlyingArray = new Array(); + } + + /// + /// Constructs a new from the given collection's elements. + /// + /// The collection of elements to construct from. + /// A new Godot Array. + public Array(IEnumerable collection) + { + ValidateVariantConversionCallbacks(); + + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + _underlyingArray = new Array(); + + foreach (T element in collection) + Add(element); + } + + /// + /// Constructs a new from the given items. + /// + /// The items to put in the new array. + /// A new Godot Array. + public Array(T[] array) : this() + { + ValidateVariantConversionCallbacks(); + + if (array == null) + throw new ArgumentNullException(nameof(array)); + + _underlyingArray = new Array(); + + foreach (T element in array) + Add(element); + } + + /// + /// Constructs a typed from an untyped . + /// + /// The untyped array to construct from. + public Array(Array array) + { + ValidateVariantConversionCallbacks(); + + _underlyingArray = array; + } + + // Explicit name to make it very clear + internal static Array CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn) + => new Array(Array.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); + + /// + /// Converts this typed to an untyped . + /// + /// The typed array to convert. + public static explicit operator Array(Array from) + { + return from?._underlyingArray; + } + + /// + /// Duplicates this . + /// + /// If , performs a deep copy. + /// A new Godot Array. + public Array Duplicate(bool deep = false) + { + return new Array(_underlyingArray.Duplicate(deep)); + } + + /// + /// Resizes this to the given size. + /// + /// The new size of the array. + /// if successful, or an error code. + public Error Resize(int newSize) + { + return _underlyingArray.Resize(newSize); + } + + /// + /// Shuffles the contents of this into a random order. + /// + public void Shuffle() + { + _underlyingArray.Shuffle(); + } + + /// + /// Concatenates these two s. + /// + /// The first array. + /// The second array. + /// A new Godot Array with the contents of both arrays. + public static Array operator +(Array left, Array right) + { + if (left == null) + { + if (right == null) + return new Array(); + + return right.Duplicate(deep: false); + } + + if (right == null) + return left.Duplicate(deep: false); + + return new Array(left._underlyingArray + right._underlyingArray); + } + + // IList + + /// + /// Returns the value at the given . + /// + /// The value at the given . + public unsafe T this[int index] + { + get + { + _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); + return _convertToManagedCallback(borrowElem); + } + set + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + var self = (godot_array)_underlyingArray.NativeValue; + godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); + godot_variant* itemPtr = &ptrw[index]; + (*itemPtr).Dispose(); + *itemPtr = _convertToVariantCallback(value); + } + } + + /// + /// Searches this for an item + /// and returns its index or -1 if not found. + /// + /// The item to search for. + /// The index of the item, or -1 if not found. + public unsafe int IndexOf(T item) + { + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); + } + + /// + /// Inserts a new item at a given position in the . + /// The position must be a valid position of an existing item, + /// or the position at the end of the array. + /// Existing items will be moved to the right. + /// + /// The index to insert at. + /// The item to insert. + public unsafe void Insert(int index, T item) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); + } + + /// + /// Removes an element from this by index. + /// + /// The index of the element to remove. + public void RemoveAt(int index) + { + _underlyingArray.RemoveAt(index); + } + + // ICollection + + /// + /// Returns the number of elements in this . + /// This is also known as the size or length of the array. + /// + /// The number of elements. + public int Count => _underlyingArray.Count; + + bool ICollection.IsReadOnly => false; + + /// + /// Adds an item to the end of this . + /// This is the same as append or push_back in GDScript. + /// + /// The item to add. + /// The new size after adding the item. + public unsafe void Add(T item) + { + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); + } + + /// + /// Erases all items from this . + /// + public void Clear() + { + _underlyingArray.Clear(); + } + + /// + /// Checks if this contains the given item. + /// + /// The item to look for. + /// Whether or not this array contains the given item. + public bool Contains(T item) => IndexOf(item) != -1; + + /// + /// Copies the elements of this to the given + /// C# array, starting at the given index. + /// + /// The C# array to copy to. + /// The index to start at. + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + } + + int count = Count; + + if (array.Length < (arrayIndex + count)) + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } + + for (int i = 0; i < count; i++) + { + array[arrayIndex] = this[i]; + arrayIndex++; + } + } + + /// + /// Removes the first occurrence of the specified value + /// from this . + /// + /// The value to remove. + /// A indicating success or failure. + public bool Remove(T item) + { + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + // IEnumerable + + /// + /// Gets an enumerator for this . + /// + /// An enumerator. + public IEnumerator GetEnumerator() + { + int count = _underlyingArray.Count; + + for (int i = 0; i < count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Converts this to a string. + /// + /// A string representation of this array. + public override string ToString() => _underlyingArray.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Array from) => Variant.CreateFrom(from); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Array(Variant from) => from.AsGodotArray(); + } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index c3d500119aa..fa8c94ed183 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Godot.NativeInterop; namespace Godot.Collections @@ -341,4 +343,380 @@ namespace Godot.Collections return Marshaling.ConvertStringToManaged(str); } } + + /// + /// Typed wrapper around Godot's Dictionary class, a dictionary of Variant + /// typed elements allocated in the engine in C++. Useful when + /// interfacing with the engine. Otherwise prefer .NET collections + /// such as . + /// + /// The type of the dictionary's keys. + /// The type of the dictionary's values. + public class Dictionary : + IDictionary, + IReadOnlyDictionary + { + // 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; + + // ReSharper restore StaticMemberInGenericType + + static unsafe Dictionary() + { + _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); + _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); + _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback(); + _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback(); + } + + 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}'"); + } + } + + private readonly Dictionary _underlyingDict; + + internal ref godot_dictionary.movable NativeValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _underlyingDict.NativeValue; + } + + /// + /// Constructs a new empty . + /// + public Dictionary() + { + ValidateVariantConversionCallbacks(); + + _underlyingDict = new Dictionary(); + } + + /// + /// Constructs a new from the given dictionary's elements. + /// + /// The dictionary to construct from. + /// A new Godot Dictionary. + public Dictionary(IDictionary dictionary) + { + ValidateVariantConversionCallbacks(); + + if (dictionary == null) + throw new ArgumentNullException(nameof(dictionary)); + + _underlyingDict = new Dictionary(); + + foreach (KeyValuePair entry in dictionary) + Add(entry.Key, entry.Value); + } + + /// + /// Constructs a new from the given dictionary's elements. + /// + /// The dictionary to construct from. + /// A new Godot Dictionary. + public Dictionary(Dictionary dictionary) + { + ValidateVariantConversionCallbacks(); + + _underlyingDict = dictionary; + } + + // Explicit name to make it very clear + internal static Dictionary CreateTakingOwnershipOfDisposableValue( + godot_dictionary nativeValueToOwn) + => new Dictionary(Dictionary.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); + + /// + /// Converts this typed to an untyped . + /// + /// The typed dictionary to convert. + public static explicit operator Dictionary(Dictionary from) + { + return from?._underlyingDict; + } + + /// + /// Duplicates this . + /// + /// If , performs a deep copy. + /// A new Godot Dictionary. + public Dictionary Duplicate(bool deep = false) + { + return new Dictionary(_underlyingDict.Duplicate(deep)); + } + + // IDictionary + + /// + /// Returns the value at the given . + /// + /// The value at the given . + public unsafe TValue this[TKey key] + { + get + { + 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); + } + else + { + throw new KeyNotFoundException(); + } + } + set + { + 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); + } + } + + /// + /// Gets the collection of keys in this . + /// + public ICollection Keys + { + get + { + godot_array keyArray; + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_keys(ref self, out keyArray); + return Array.CreateTakingOwnershipOfDisposableValue(keyArray); + } + } + + /// + /// Gets the collection of elements in this . + /// + public ICollection Values + { + get + { + godot_array valuesArray; + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray); + return Array.CreateTakingOwnershipOfDisposableValue(valuesArray); + } + } + + IEnumerable IReadOnlyDictionary.Keys => Keys; + + IEnumerable IReadOnlyDictionary.Values => Values; + + private unsafe KeyValuePair GetKeyValuePair(int index) + { + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index, + out godot_variant key, + out godot_variant value); + using (key) + using (value) + { + return new KeyValuePair( + _convertKeyToManagedCallback(key), + _convertValueToManagedCallback(value)); + } + } + + /// + /// Adds an object at key + /// to this . + /// + /// The key at which to add the object. + /// The object to add. + public unsafe void Add(TKey key, TValue value) + { + 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); + NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); + } + + /// + /// Checks if this contains the given key. + /// + /// The key to look for. + /// Whether or not this dictionary contains the given key. + public unsafe bool ContainsKey(TKey key) + { + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool(); + } + + /// + /// Removes an element from this by key. + /// + /// The key of the element to remove. + public unsafe bool Remove(TKey key) + { + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); + } + + /// + /// Gets the object at the given . + /// + /// The key of the element to get. + /// The value at the given . + /// If an object was found for the given . + public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + 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; + + return found; + } + + // ICollection> + + /// + /// Returns the number of elements in this . + /// This is also known as the size or length of the dictionary. + /// + /// The number of elements. + public int Count => _underlyingDict.Count; + + bool ICollection>.IsReadOnly => false; + + void ICollection>.Add(KeyValuePair item) + => Add(item.Key, item.Value); + + /// + /// Erases all the items from this . + /// + public void Clear() => _underlyingDict.Clear(); + + unsafe bool ICollection>.Contains(KeyValuePair item) + { + 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(); + + using (retValue) + { + if (!found) + return false; + + using var variantValue = _convertValueToVariantCallback(item.Value); + return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); + } + } + + /// + /// Copies the elements of this to the given + /// untyped C# array, starting at the given index. + /// + /// The array to copy to. + /// The index to start at. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + + int count = Count; + + if (array.Length < (arrayIndex + count)) + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + + for (int i = 0; i < count; i++) + { + array[arrayIndex] = GetKeyValuePair(i); + arrayIndex++; + } + } + + unsafe bool ICollection>.Remove(KeyValuePair item) + { + 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(); + + using (retValue) + { + if (!found) + return false; + + using var variantValue = _convertValueToVariantCallback(item.Value); + if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) + { + return NativeFuncs.godotsharp_dictionary_remove_key( + ref self, variantKey).ToBool(); + } + + return false; + } + } + + // IEnumerable> + + /// + /// Gets an enumerator for this . + /// + /// An enumerator. + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; i++) + { + yield return GetKeyValuePair(i); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Converts this to a string. + /// + /// A string representation of this dictionary. + public override string ToString() => _underlyingDict.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Dictionary from) => Variant.CreateFrom(from); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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 new file mode 100644 index 00000000000..2b5bf2e1429 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs @@ -0,0 +1,976 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Godot.NativeInterop; + +internal static unsafe class VariantConversionCallbacks +{ + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + internal static delegate* GetToVariantCallback() + { + static godot_variant FromBool(in bool @bool) => + VariantUtils.CreateFromBool(@bool); + + static godot_variant FromChar(in char @char) => + VariantUtils.CreateFromInt(@char); + + static godot_variant FromInt8(in sbyte @int8) => + VariantUtils.CreateFromInt(@int8); + + static godot_variant FromInt16(in short @int16) => + VariantUtils.CreateFromInt(@int16); + + static godot_variant FromInt32(in int @int32) => + VariantUtils.CreateFromInt(@int32); + + static godot_variant FromInt64(in long @int64) => + VariantUtils.CreateFromInt(@int64); + + static godot_variant FromUInt8(in byte @uint8) => + VariantUtils.CreateFromInt(@uint8); + + static godot_variant FromUInt16(in ushort @uint16) => + VariantUtils.CreateFromInt(@uint16); + + static godot_variant FromUInt32(in uint @uint32) => + VariantUtils.CreateFromInt(@uint32); + + static godot_variant FromUInt64(in ulong @uint64) => + VariantUtils.CreateFromInt(@uint64); + + static godot_variant FromFloat(in float @float) => + VariantUtils.CreateFromFloat(@float); + + static godot_variant FromDouble(in double @double) => + VariantUtils.CreateFromFloat(@double); + + static godot_variant FromVector2(in Vector2 @vector2) => + VariantUtils.CreateFromVector2(@vector2); + + static godot_variant FromVector2I(in Vector2i vector2I) => + VariantUtils.CreateFromVector2i(vector2I); + + static godot_variant FromRect2(in Rect2 @rect2) => + VariantUtils.CreateFromRect2(@rect2); + + static godot_variant FromRect2I(in Rect2i rect2I) => + VariantUtils.CreateFromRect2i(rect2I); + + static godot_variant FromTransform2D(in Transform2D @transform2D) => + VariantUtils.CreateFromTransform2D(@transform2D); + + static godot_variant FromVector3(in Vector3 @vector3) => + VariantUtils.CreateFromVector3(@vector3); + + static godot_variant FromVector3I(in Vector3i vector3I) => + VariantUtils.CreateFromVector3i(vector3I); + + static godot_variant FromBasis(in Basis @basis) => + VariantUtils.CreateFromBasis(@basis); + + static godot_variant FromQuaternion(in Quaternion @quaternion) => + VariantUtils.CreateFromQuaternion(@quaternion); + + static godot_variant FromTransform3D(in Transform3D @transform3d) => + VariantUtils.CreateFromTransform3D(@transform3d); + + static godot_variant FromAabb(in AABB @aabb) => + VariantUtils.CreateFromAABB(@aabb); + + static godot_variant FromColor(in Color @color) => + VariantUtils.CreateFromColor(@color); + + static godot_variant FromPlane(in Plane @plane) => + VariantUtils.CreateFromPlane(@plane); + + static godot_variant FromCallable(in Callable @callable) => + VariantUtils.CreateFromCallable(@callable); + + static godot_variant FromSignalInfo(in SignalInfo @signalInfo) => + VariantUtils.CreateFromSignalInfo(@signalInfo); + + static godot_variant FromString(in string @string) => + VariantUtils.CreateFromString(@string); + + static godot_variant FromByteArray(in byte[] byteArray) => + VariantUtils.CreateFromPackedByteArray(byteArray); + + static godot_variant FromInt32Array(in int[] int32Array) => + VariantUtils.CreateFromPackedInt32Array(int32Array); + + static godot_variant FromInt64Array(in long[] int64Array) => + VariantUtils.CreateFromPackedInt64Array(int64Array); + + static godot_variant FromFloatArray(in float[] floatArray) => + VariantUtils.CreateFromPackedFloat32Array(floatArray); + + static godot_variant FromDoubleArray(in double[] doubleArray) => + VariantUtils.CreateFromPackedFloat64Array(doubleArray); + + static godot_variant FromStringArray(in string[] stringArray) => + VariantUtils.CreateFromPackedStringArray(stringArray); + + static godot_variant FromVector2Array(in Vector2[] vector2Array) => + VariantUtils.CreateFromPackedVector2Array(vector2Array); + + static godot_variant FromVector3Array(in Vector3[] vector3Array) => + VariantUtils.CreateFromPackedVector3Array(vector3Array); + + static godot_variant FromColorArray(in Color[] colorArray) => + VariantUtils.CreateFromPackedColorArray(colorArray); + + static godot_variant FromStringNameArray(in StringName[] stringNameArray) => + VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); + + static godot_variant FromNodePathArray(in NodePath[] nodePathArray) => + VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); + + static godot_variant FromRidArray(in RID[] ridArray) => + VariantUtils.CreateFromSystemArrayOfRID(ridArray); + + static godot_variant FromGodotObject(in Godot.Object godotObject) => + VariantUtils.CreateFromGodotObject(godotObject); + + static godot_variant FromStringName(in StringName stringName) => + VariantUtils.CreateFromStringName(stringName); + + static godot_variant FromNodePath(in NodePath nodePath) => + VariantUtils.CreateFromNodePath(nodePath); + + static godot_variant FromRid(in RID rid) => + VariantUtils.CreateFromRID(rid); + + static godot_variant FromGodotDictionary(in Collections.Dictionary godotDictionary) => + VariantUtils.CreateFromDictionary(godotDictionary); + + static godot_variant FromGodotArray(in Collections.Array godotArray) => + VariantUtils.CreateFromArray(godotArray); + + static godot_variant FromVariant(in Variant variant) => + NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); + + var typeOfT = typeof(T); + + if (typeOfT == typeof(bool)) + { + return (delegate* )(delegate* ) + &FromBool; + } + + if (typeOfT == typeof(char)) + { + return (delegate* )(delegate* ) + &FromChar; + } + + if (typeOfT == typeof(sbyte)) + { + return (delegate* )(delegate* ) + &FromInt8; + } + + if (typeOfT == typeof(short)) + { + return (delegate* )(delegate* ) + &FromInt16; + } + + if (typeOfT == typeof(int)) + { + return (delegate* )(delegate* ) + &FromInt32; + } + + if (typeOfT == typeof(long)) + { + return (delegate* )(delegate* ) + &FromInt64; + } + + if (typeOfT == typeof(byte)) + { + return (delegate* )(delegate* ) + &FromUInt8; + } + + if (typeOfT == typeof(ushort)) + { + return (delegate* )(delegate* ) + &FromUInt16; + } + + if (typeOfT == typeof(uint)) + { + return (delegate* )(delegate* ) + &FromUInt32; + } + + if (typeOfT == typeof(ulong)) + { + return (delegate* )(delegate* ) + &FromUInt64; + } + + if (typeOfT == typeof(float)) + { + return (delegate* )(delegate* ) + &FromFloat; + } + + if (typeOfT == typeof(double)) + { + return (delegate* )(delegate* ) + &FromDouble; + } + + if (typeOfT == typeof(Vector2)) + { + return (delegate* )(delegate* ) + &FromVector2; + } + + if (typeOfT == typeof(Vector2i)) + { + return (delegate* )(delegate* ) + &FromVector2I; + } + + if (typeOfT == typeof(Rect2)) + { + return (delegate* )(delegate* ) + &FromRect2; + } + + if (typeOfT == typeof(Rect2i)) + { + return (delegate* )(delegate* ) + &FromRect2I; + } + + if (typeOfT == typeof(Transform2D)) + { + return (delegate* )(delegate* ) + &FromTransform2D; + } + + if (typeOfT == typeof(Vector3)) + { + return (delegate* )(delegate* ) + &FromVector3; + } + + if (typeOfT == typeof(Vector3i)) + { + return (delegate* )(delegate* ) + &FromVector3I; + } + + if (typeOfT == typeof(Basis)) + { + return (delegate* )(delegate* ) + &FromBasis; + } + + if (typeOfT == typeof(Quaternion)) + { + return (delegate* )(delegate* ) + &FromQuaternion; + } + + if (typeOfT == typeof(Transform3D)) + { + return (delegate* )(delegate* ) + &FromTransform3D; + } + + if (typeOfT == typeof(AABB)) + { + return (delegate* )(delegate* ) + &FromAabb; + } + + if (typeOfT == typeof(Color)) + { + return (delegate* )(delegate* ) + &FromColor; + } + + if (typeOfT == typeof(Plane)) + { + return (delegate* )(delegate* ) + &FromPlane; + } + + if (typeOfT == typeof(Callable)) + { + return (delegate* )(delegate* ) + &FromCallable; + } + + if (typeOfT == typeof(SignalInfo)) + { + return (delegate* )(delegate* ) + &FromSignalInfo; + } + + if (typeOfT.IsEnum) + { + var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); + + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + { + return (delegate* )(delegate* ) + &FromInt8; + } + case TypeCode.Int16: + { + return (delegate* )(delegate* ) + &FromInt16; + } + case TypeCode.Int32: + { + return (delegate* )(delegate* ) + &FromInt32; + } + case TypeCode.Int64: + { + return (delegate* )(delegate* ) + &FromInt64; + } + case TypeCode.Byte: + { + return (delegate* )(delegate* ) + &FromUInt8; + } + case TypeCode.UInt16: + { + return (delegate* )(delegate* ) + &FromUInt16; + } + case TypeCode.UInt32: + { + return (delegate* )(delegate* ) + &FromUInt32; + } + case TypeCode.UInt64: + { + return (delegate* )(delegate* ) + &FromUInt64; + } + default: + return null; + } + } + + if (typeOfT == typeof(string)) + { + return (delegate* )(delegate* ) + &FromString; + } + + if (typeOfT == typeof(byte[])) + { + return (delegate* )(delegate* ) + &FromByteArray; + } + + if (typeOfT == typeof(int[])) + { + return (delegate* )(delegate* ) + &FromInt32Array; + } + + if (typeOfT == typeof(long[])) + { + return (delegate* )(delegate* ) + &FromInt64Array; + } + + if (typeOfT == typeof(float[])) + { + return (delegate* )(delegate* ) + &FromFloatArray; + } + + if (typeOfT == typeof(double[])) + { + return (delegate* )(delegate* ) + &FromDoubleArray; + } + + if (typeOfT == typeof(string[])) + { + return (delegate* )(delegate* ) + &FromStringArray; + } + + if (typeOfT == typeof(Vector2[])) + { + return (delegate* )(delegate* ) + &FromVector2Array; + } + + if (typeOfT == typeof(Vector3[])) + { + return (delegate* )(delegate* ) + &FromVector3Array; + } + + if (typeOfT == typeof(Color[])) + { + return (delegate* )(delegate* ) + &FromColorArray; + } + + if (typeOfT == typeof(StringName[])) + { + return (delegate* )(delegate* ) + &FromStringNameArray; + } + + if (typeOfT == typeof(NodePath[])) + { + return (delegate* )(delegate* ) + &FromNodePathArray; + } + + if (typeOfT == typeof(RID[])) + { + return (delegate* )(delegate* ) + &FromRidArray; + } + + if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) + { + return (delegate* )(delegate* ) + &FromGodotObject; + } + + if (typeOfT == typeof(StringName)) + { + return (delegate* )(delegate* ) + &FromStringName; + } + + if (typeOfT == typeof(NodePath)) + { + return (delegate* )(delegate* ) + &FromNodePath; + } + + if (typeOfT == typeof(RID)) + { + return (delegate* )(delegate* ) + &FromRid; + } + + if (typeOfT == typeof(Godot.Collections.Dictionary)) + { + return (delegate* )(delegate* ) + &FromGodotDictionary; + } + + if (typeOfT == typeof(Godot.Collections.Array)) + { + return (delegate* )(delegate* ) + &FromGodotArray; + } + + if (typeOfT == typeof(Variant)) + { + return (delegate* )(delegate* ) + &FromVariant; + } + + return null; + } + + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + internal static delegate* GetToManagedCallback() + { + static bool ToBool(in godot_variant variant) => + VariantUtils.ConvertToBool(variant); + + static char ToChar(in godot_variant variant) => + VariantUtils.ConvertToChar(variant); + + static sbyte ToInt8(in godot_variant variant) => + VariantUtils.ConvertToInt8(variant); + + static short ToInt16(in godot_variant variant) => + VariantUtils.ConvertToInt16(variant); + + static int ToInt32(in godot_variant variant) => + VariantUtils.ConvertToInt32(variant); + + static long ToInt64(in godot_variant variant) => + VariantUtils.ConvertToInt64(variant); + + static byte ToUInt8(in godot_variant variant) => + VariantUtils.ConvertToUInt8(variant); + + static ushort ToUInt16(in godot_variant variant) => + VariantUtils.ConvertToUInt16(variant); + + static uint ToUInt32(in godot_variant variant) => + VariantUtils.ConvertToUInt32(variant); + + static ulong ToUInt64(in godot_variant variant) => + VariantUtils.ConvertToUInt64(variant); + + static float ToFloat(in godot_variant variant) => + VariantUtils.ConvertToFloat32(variant); + + static double ToDouble(in godot_variant variant) => + VariantUtils.ConvertToFloat64(variant); + + static Vector2 ToVector2(in godot_variant variant) => + VariantUtils.ConvertToVector2(variant); + + static Vector2i ToVector2I(in godot_variant variant) => + VariantUtils.ConvertToVector2i(variant); + + static Rect2 ToRect2(in godot_variant variant) => + VariantUtils.ConvertToRect2(variant); + + static Rect2i ToRect2I(in godot_variant variant) => + VariantUtils.ConvertToRect2i(variant); + + static Transform2D ToTransform2D(in godot_variant variant) => + VariantUtils.ConvertToTransform2D(variant); + + static Vector3 ToVector3(in godot_variant variant) => + VariantUtils.ConvertToVector3(variant); + + static Vector3i ToVector3I(in godot_variant variant) => + VariantUtils.ConvertToVector3i(variant); + + static Basis ToBasis(in godot_variant variant) => + VariantUtils.ConvertToBasis(variant); + + static Quaternion ToQuaternion(in godot_variant variant) => + VariantUtils.ConvertToQuaternion(variant); + + static Transform3D ToTransform3D(in godot_variant variant) => + VariantUtils.ConvertToTransform3D(variant); + + static AABB ToAabb(in godot_variant variant) => + VariantUtils.ConvertToAABB(variant); + + static Color ToColor(in godot_variant variant) => + VariantUtils.ConvertToColor(variant); + + static Plane ToPlane(in godot_variant variant) => + VariantUtils.ConvertToPlane(variant); + + static Callable ToCallable(in godot_variant variant) => + VariantUtils.ConvertToCallableManaged(variant); + + static SignalInfo ToSignalInfo(in godot_variant variant) => + VariantUtils.ConvertToSignalInfo(variant); + + static string ToString(in godot_variant variant) => + VariantUtils.ConvertToStringObject(variant); + + static byte[] ToByteArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedByteArrayToSystemArray(variant); + + static int[] ToInt32Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(variant); + + static long[] ToInt64Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(variant); + + static float[] ToFloatArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(variant); + + static double[] ToDoubleArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(variant); + + static string[] ToStringArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedStringArrayToSystemArray(variant); + + static Vector2[] ToVector2Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(variant); + + static Vector3[] ToVector3Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(variant); + + static Color[] ToColorArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedColorArrayToSystemArray(variant); + + static StringName[] ToStringNameArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfStringName(variant); + + static NodePath[] ToNodePathArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfNodePath(variant); + + static RID[] ToRidArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfRID(variant); + + static Godot.Object ToGodotObject(in godot_variant variant) => + VariantUtils.ConvertToGodotObject(variant); + + static StringName ToStringName(in godot_variant variant) => + VariantUtils.ConvertToStringNameObject(variant); + + static NodePath ToNodePath(in godot_variant variant) => + VariantUtils.ConvertToNodePathObject(variant); + + static RID ToRid(in godot_variant variant) => + VariantUtils.ConvertToRID(variant); + + static Collections.Dictionary ToGodotDictionary(in godot_variant variant) => + VariantUtils.ConvertToDictionaryObject(variant); + + static Collections.Array ToGodotArray(in godot_variant variant) => + VariantUtils.ConvertToArrayObject(variant); + + static Variant ToVariant(in godot_variant variant) => + Variant.CreateCopyingBorrowed(variant); + + var typeOfT = typeof(T); + + // ReSharper disable RedundantCast + // Rider is being stupid here. These casts are definitely needed. We get build errors without them. + + if (typeOfT == typeof(bool)) + { + return (delegate* )(delegate* ) + &ToBool; + } + + if (typeOfT == typeof(char)) + { + return (delegate* )(delegate* ) + &ToChar; + } + + if (typeOfT == typeof(sbyte)) + { + return (delegate* )(delegate* ) + &ToInt8; + } + + if (typeOfT == typeof(short)) + { + return (delegate* )(delegate* ) + &ToInt16; + } + + if (typeOfT == typeof(int)) + { + return (delegate* )(delegate* ) + &ToInt32; + } + + if (typeOfT == typeof(long)) + { + return (delegate* )(delegate* ) + &ToInt64; + } + + if (typeOfT == typeof(byte)) + { + return (delegate* )(delegate* ) + &ToUInt8; + } + + if (typeOfT == typeof(ushort)) + { + return (delegate* )(delegate* ) + &ToUInt16; + } + + if (typeOfT == typeof(uint)) + { + return (delegate* )(delegate* ) + &ToUInt32; + } + + if (typeOfT == typeof(ulong)) + { + return (delegate* )(delegate* ) + &ToUInt64; + } + + if (typeOfT == typeof(float)) + { + return (delegate* )(delegate* ) + &ToFloat; + } + + if (typeOfT == typeof(double)) + { + return (delegate* )(delegate* ) + &ToDouble; + } + + if (typeOfT == typeof(Vector2)) + { + return (delegate* )(delegate* ) + &ToVector2; + } + + if (typeOfT == typeof(Vector2i)) + { + return (delegate* )(delegate* ) + &ToVector2I; + } + + if (typeOfT == typeof(Rect2)) + { + return (delegate* )(delegate* ) + &ToRect2; + } + + if (typeOfT == typeof(Rect2i)) + { + return (delegate* )(delegate* ) + &ToRect2I; + } + + if (typeOfT == typeof(Transform2D)) + { + return (delegate* )(delegate* ) + &ToTransform2D; + } + + if (typeOfT == typeof(Vector3)) + { + return (delegate* )(delegate* ) + &ToVector3; + } + + if (typeOfT == typeof(Vector3i)) + { + return (delegate* )(delegate* ) + &ToVector3I; + } + + if (typeOfT == typeof(Basis)) + { + return (delegate* )(delegate* ) + &ToBasis; + } + + if (typeOfT == typeof(Quaternion)) + { + return (delegate* )(delegate* ) + &ToQuaternion; + } + + if (typeOfT == typeof(Transform3D)) + { + return (delegate* )(delegate* ) + &ToTransform3D; + } + + if (typeOfT == typeof(AABB)) + { + return (delegate* )(delegate* ) + &ToAabb; + } + + if (typeOfT == typeof(Color)) + { + return (delegate* )(delegate* ) + &ToColor; + } + + if (typeOfT == typeof(Plane)) + { + return (delegate* )(delegate* ) + &ToPlane; + } + + if (typeOfT == typeof(Callable)) + { + return (delegate* )(delegate* ) + &ToCallable; + } + + if (typeOfT == typeof(SignalInfo)) + { + return (delegate* )(delegate* ) + &ToSignalInfo; + } + + if (typeOfT.IsEnum) + { + var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); + + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + { + return (delegate* )(delegate* ) + &ToInt8; + } + case TypeCode.Int16: + { + return (delegate* )(delegate* ) + &ToInt16; + } + case TypeCode.Int32: + { + return (delegate* )(delegate* ) + &ToInt32; + } + case TypeCode.Int64: + { + return (delegate* )(delegate* ) + &ToInt64; + } + case TypeCode.Byte: + { + return (delegate* )(delegate* ) + &ToUInt8; + } + case TypeCode.UInt16: + { + return (delegate* )(delegate* ) + &ToUInt16; + } + case TypeCode.UInt32: + { + return (delegate* )(delegate* ) + &ToUInt32; + } + case TypeCode.UInt64: + { + return (delegate* )(delegate* ) + &ToUInt64; + } + default: + return null; + } + } + + if (typeOfT == typeof(string)) + { + return (delegate* )(delegate* ) + &ToString; + } + + if (typeOfT == typeof(byte[])) + { + return (delegate* )(delegate* ) + &ToByteArray; + } + + if (typeOfT == typeof(int[])) + { + return (delegate* )(delegate* ) + &ToInt32Array; + } + + if (typeOfT == typeof(long[])) + { + return (delegate* )(delegate* ) + &ToInt64Array; + } + + if (typeOfT == typeof(float[])) + { + return (delegate* )(delegate* ) + &ToFloatArray; + } + + if (typeOfT == typeof(double[])) + { + return (delegate* )(delegate* ) + &ToDoubleArray; + } + + if (typeOfT == typeof(string[])) + { + return (delegate* )(delegate* ) + &ToStringArray; + } + + if (typeOfT == typeof(Vector2[])) + { + return (delegate* )(delegate* ) + &ToVector2Array; + } + + if (typeOfT == typeof(Vector3[])) + { + return (delegate* )(delegate* ) + &ToVector3Array; + } + + if (typeOfT == typeof(Color[])) + { + return (delegate* )(delegate* ) + &ToColorArray; + } + + if (typeOfT == typeof(StringName[])) + { + return (delegate* )(delegate* ) + &ToStringNameArray; + } + + if (typeOfT == typeof(NodePath[])) + { + return (delegate* )(delegate* ) + &ToNodePathArray; + } + + if (typeOfT == typeof(RID[])) + { + return (delegate* )(delegate* ) + &ToRidArray; + } + + if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) + { + return (delegate* )(delegate* ) + &ToGodotObject; + } + + if (typeOfT == typeof(StringName)) + { + return (delegate* )(delegate* ) + &ToStringName; + } + + if (typeOfT == typeof(NodePath)) + { + return (delegate* )(delegate* ) + &ToNodePath; + } + + if (typeOfT == typeof(RID)) + { + return (delegate* )(delegate* ) + &ToRid; + } + + if (typeOfT == typeof(Godot.Collections.Dictionary)) + { + return (delegate* )(delegate* ) + &ToGodotDictionary; + } + + if (typeOfT == typeof(Godot.Collections.Array)) + { + return (delegate* )(delegate* ) + &ToGodotArray; + } + + if (typeOfT == typeof(Variant)) + { + return (delegate* )(delegate* ) + &ToVariant; + } + + // ReSharper restore RedundantCast + + return null; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs index 1ce89659399..491ccf904ef 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -238,6 +238,10 @@ namespace Godot.NativeInterop public static godot_variant CreateFromArray(Collections.Array? from) => from != null ? CreateFromArray((godot_array)from.NativeValue) : default; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromArray(Array? from) + => from != null ? CreateFromArray((godot_array)((Collections.Array)from).NativeValue) : default; + public static godot_variant CreateFromDictionary(godot_dictionary from) { NativeFuncs.godotsharp_variant_new_dictionary(out godot_variant ret, from); @@ -248,6 +252,10 @@ namespace Godot.NativeInterop public static godot_variant CreateFromDictionary(Dictionary? from) => from != null ? CreateFromDictionary((godot_dictionary)from.NativeValue) : default; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromDictionary(Dictionary? from) + => from != null ? CreateFromDictionary((godot_dictionary)((Dictionary)from).NativeValue) : default; + public static godot_variant CreateFromStringName(godot_string_name from) { NativeFuncs.godotsharp_variant_new_string_name(out godot_variant ret, from); @@ -496,6 +504,10 @@ namespace Godot.NativeInterop public static Collections.Array ConvertToArrayObject(in godot_variant p_var) => Collections.Array.CreateTakingOwnershipOfDisposableValue(ConvertToArray(p_var)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Array ConvertToArrayObject(in godot_variant p_var) + => Array.CreateTakingOwnershipOfDisposableValue(ConvertToArray(p_var)); + public static godot_dictionary ConvertToDictionary(in godot_variant p_var) => p_var.Type == Variant.Type.Dictionary ? NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary) : @@ -505,6 +517,10 @@ namespace Godot.NativeInterop public static Dictionary ConvertToDictionaryObject(in godot_variant p_var) => Dictionary.CreateTakingOwnershipOfDisposableValue(ConvertToDictionary(p_var)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary ConvertToDictionaryObject(in godot_variant p_var) + => Dictionary.CreateTakingOwnershipOfDisposableValue(ConvertToDictionary(p_var)); + public static byte[] ConvertAsPackedByteArrayToSystemArray(in godot_variant p_var) { using var packedArray = NativeFuncs.godotsharp_variant_as_packed_byte_array(p_var); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index d1ff6ade8af..c7881c74042 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -94,6 +94,7 @@ + diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs index 7c4df291ac1..eb8b0611206 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs @@ -283,6 +283,14 @@ public partial struct Variant : IDisposable where T : Godot.Object => VariantUtils.ConvertToSystemArrayOfGodotObject((godot_variant)NativeVar); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Dictionary AsGodotDictionary() => + VariantUtils.ConvertToDictionaryObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Array AsGodotArray() => + VariantUtils.ConvertToArrayObject((godot_variant)NativeVar); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public StringName[] AsSystemArrayOfStringName() => VariantUtils.ConvertToSystemArrayOfStringName((godot_variant)NativeVar); @@ -594,6 +602,14 @@ public partial struct Variant : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Variant CreateFrom(Godot.Object[] from) => from; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Collections.Dictionary from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromDictionary(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Collections.Array from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Variant CreateFrom(Span from) => from;