diff options
Diffstat (limited to 'modules/mono/glue/GodotSharp')
23 files changed, 1570 insertions, 1775 deletions
diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs index 16e96c725a..d3726d69f0 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs @@ -12,7 +12,7 @@ internal static class Common { string message = "Missing partial modifier on declaration of type '" + - $"{symbol.FullQualifiedName()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; + $"{symbol.FullQualifiedNameOmitGlobal()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; string description = $"{message}. Classes with attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' " + "must be declared with the partial modifier."; @@ -39,7 +39,7 @@ internal static class Common .GetDeclaredSymbol(outerTypeDeclSyntax); string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? - namedTypeSymbol.FullQualifiedName() : + namedTypeSymbol.FullQualifiedNameOmitGlobal() : "type not found"; string message = diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs index fac362479a..37f7005d01 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs @@ -94,13 +94,6 @@ internal static class ExtensionMethods }; } - private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = - SymbolDisplayFormat.FullyQualifiedFormat - .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - - public static string FullQualifiedName(this ITypeSymbol symbol) - => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); - public static string NameWithTypeParameters(this INamedTypeSymbol symbol) { return symbol.IsGenericType ? @@ -108,8 +101,25 @@ internal static class ExtensionMethods symbol.Name; } - public static string FullQualifiedName(this INamespaceSymbol symbol) - => symbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + private static SymbolDisplayFormat FullyQualifiedFormatIncludeGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Included); + + public static string FullQualifiedNameOmitGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameOmitGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameIncludeGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatIncludeGlobal); + + public static string FullQualifiedNameIncludeGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatIncludeGlobal); public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) => qualifiedName diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs index da578309bc..3226ca79e5 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs @@ -96,7 +96,7 @@ internal class GenerateUnmanagedCallbacksAttribute : Attribute INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; @@ -144,7 +144,7 @@ using Godot.NativeInterop; source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); source.Append($"unsafe partial class {symbol.Name}\n"); source.Append("{\n"); - source.Append($" private static {data.FuncStructSymbol.FullQualifiedName()} _unmanagedCallbacks;\n\n"); + source.Append($" private static {data.FuncStructSymbol.FullQualifiedNameIncludeGlobal()} _unmanagedCallbacks;\n\n"); foreach (var callback in data.Methods) { @@ -159,7 +159,7 @@ using Godot.NativeInterop; source.Append("static "); source.Append("partial "); - source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal()); source.Append(' '); source.Append(callback.Name); source.Append('('); @@ -228,7 +228,7 @@ using Godot.NativeInterop; if (!callback.ReturnsVoid) { if (methodSourceAfterCall.Length != 0) - source.Append($"{callback.ReturnType.FullQualifiedName()} ret = "); + source.Append($"{callback.ReturnType.FullQualifiedNameIncludeGlobal()} ret = "); else source.Append("return "); } @@ -267,7 +267,7 @@ using Godot.NativeInterop; source.Append("\n\n#pragma warning restore CA1707\n"); - context.AddSource($"{data.NativeTypeSymbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + context.AddSource($"{data.NativeTypeSymbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); } @@ -277,7 +277,7 @@ using Godot.NativeInterop; INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; @@ -338,18 +338,18 @@ using Godot.NativeInterop; // just pass it by-ref and let it be pinned. AppendRefKind(source, parameter.RefKind) .Append(' ') - .Append(parameter.Type.FullQualifiedName()); + .Append(parameter.Type.FullQualifiedNameIncludeGlobal()); } } else { - source.Append(parameter.Type.FullQualifiedName()); + source.Append(parameter.Type.FullQualifiedNameIncludeGlobal()); } source.Append(", "); } - source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal()); source.Append($"> {callback.Name};\n"); } @@ -372,12 +372,12 @@ using Godot.NativeInterop; source.Append("\n#pragma warning restore CA1707\n"); - context.AddSource($"{symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + context.AddSource($"{symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); } private static bool IsGodotInteropStruct(ITypeSymbol type) => - GodotInteropStructs.Contains(type.FullQualifiedName()); + GodotInteropStructs.Contains(type.FullQualifiedNameOmitGlobal()); private static bool IsByRefParameter(IParameterSymbol parameter) => parameter.RefKind is RefKind.In or RefKind.Out or RefKind.Ref; @@ -393,7 +393,7 @@ using Godot.NativeInterop; private static void AppendPointerType(StringBuilder source, ITypeSymbol type) { - source.Append(type.FullQualifiedName()); + source.Append(type.FullQualifiedNameIncludeGlobal()); source.Append('*'); } @@ -426,7 +426,7 @@ using Godot.NativeInterop; { varName = $"{parameter.Name}_copy"; - source.Append(parameter.Type.FullQualifiedName()); + source.Append(parameter.Type.FullQualifiedNameIncludeGlobal()); source.Append(' '); source.Append(varName); if (parameter.RefKind is RefKind.In or RefKind.Ref) diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs index 8308bada24..4ce02d221e 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -28,17 +28,24 @@ namespace GodotPlugins get => _pluginLoadContext?.AssemblyLoadedPath; } + public bool IsCollectible + { + [MethodImpl(MethodImplOptions.NoInlining)] + get => _pluginLoadContext?.IsCollectible ?? false; + } + [MethodImpl(MethodImplOptions.NoInlining)] public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName( AssemblyName assemblyName, string pluginPath, ICollection<string> sharedAssemblies, - AssemblyLoadContext mainLoadContext + AssemblyLoadContext mainLoadContext, + bool isCollectible ) { var wrapper = new PluginLoadContextWrapper(); wrapper._pluginLoadContext = new PluginLoadContext( - pluginPath, sharedAssemblies, mainLoadContext); + pluginPath, sharedAssemblies, mainLoadContext, isCollectible); var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName); return (assembly, wrapper); } @@ -61,6 +68,7 @@ namespace GodotPlugins private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly; private static Assembly? _editorApiAssembly; private static PluginLoadContextWrapper? _projectLoadContext; + private static bool _editorHint = false; private static readonly AssemblyLoadContext MainLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? @@ -77,15 +85,17 @@ namespace GodotPlugins { try { + _editorHint = editorHint.ToBool(); + _dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport; SharedAssemblies.Add(CoreApiAssembly.GetName()); NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver); - AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool()); + AlcReloadCfg.Configure(alcReloadEnabled: _editorHint); NativeFuncs.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); - if (editorHint.ToBool()) + if (_editorHint) { _editorApiAssembly = Assembly.Load("GodotSharpEditor"); SharedAssemblies.Add(_editorApiAssembly.GetName()); @@ -128,7 +138,7 @@ namespace GodotPlugins string assemblyPath = new(nAssemblyPath); - (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath); + (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath, isCollectible: _editorHint); string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath; *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath); @@ -155,7 +165,7 @@ namespace GodotPlugins if (_editorApiAssembly == null) throw new InvalidOperationException("The Godot editor API assembly is not loaded."); - var (assembly, _) = LoadPlugin(assemblyPath); + var (assembly, _) = LoadPlugin(assemblyPath, isCollectible: _editorHint); NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!); @@ -180,7 +190,7 @@ namespace GodotPlugins } } - private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath) + private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath, bool isCollectible) { string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); @@ -194,7 +204,7 @@ namespace GodotPlugins } return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName( - new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext); + new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext, isCollectible); } [UnmanagedCallersOnly] @@ -218,6 +228,12 @@ namespace GodotPlugins if (pluginLoadContext == null) return true; + if (!pluginLoadContext.IsCollectible) + { + Console.Error.WriteLine("Cannot unload a non-collectible assembly load context."); + return false; + } + Console.WriteLine("Unloading assembly load context..."); var alcWeakReference = pluginLoadContext.CreateWeakReference(); diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs index dcd572c65e..344b76a202 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs @@ -15,8 +15,8 @@ namespace GodotPlugins public string? AssemblyLoadedPath { get; private set; } public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies, - AssemblyLoadContext mainLoadContext) - : base(isCollectible: true) + AssemblyLoadContext mainLoadContext, bool isCollectible) + : base(isCollectible) { _resolver = new AssemblyDependencyResolver(pluginPath); _sharedAssemblies = sharedAssemblies; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index f1b46e293b..1b9f63c22d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -474,6 +474,11 @@ namespace Godot.Collections } } + internal interface IGenericGodotArray + { + public Array UnderlyingArray { get; } + } + /// <summary> /// Typed wrapper around Godot's Array class, an array of Variant /// typed elements allocated in the engine in C++. Useful when @@ -487,7 +492,8 @@ namespace Godot.Collections IList<T>, IReadOnlyList<T>, ICollection<T>, - IEnumerable<T> + IEnumerable<T>, + IGenericGodotArray { private static godot_variant ToVariantFunc(in Array<T> godotArray) => VariantUtils.CreateFromArray(godotArray); @@ -495,39 +501,16 @@ 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; + Array IGenericGodotArray.UnderlyingArray => _underlyingArray; + internal ref godot_array.movable NativeValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -539,8 +522,6 @@ namespace Godot.Collections /// </summary> public Array() { - ValidateVariantConversionCallbacks(); - _underlyingArray = new Array(); } @@ -551,8 +532,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 +548,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 +563,6 @@ namespace Godot.Collections /// <param name="array">The untyped array to construct from.</param> public Array(Array array) { - ValidateVariantConversionCallbacks(); - _underlyingArray = array; } @@ -665,7 +640,7 @@ namespace Godot.Collections get { _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); - return ConvertToManagedCallback(borrowElem); + return VariantUtils.ConvertTo<T>(borrowElem); } set { @@ -675,7 +650,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 +660,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 +675,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 +711,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); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index d83cf43eb2..d6fad391b6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -827,7 +827,7 @@ namespace Godot.Bridge { // Weird limitation, hence the need for aux: // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." - var aux = stackalloc godotsharp_property_info[length]; + var aux = stackalloc godotsharp_property_info[stackMaxLength]; interopProperties = aux; } else @@ -947,7 +947,7 @@ namespace Godot.Bridge { // Weird limitation, hence the need for aux: // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." - var aux = stackalloc godotsharp_property_def_val_pair[length]; + var aux = stackalloc godotsharp_property_def_val_pair[stackMaxLength]; interopDefaultValues = aux; } else diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index f9309ca13e..23b0aa9204 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -77,7 +77,7 @@ namespace Godot _trampoline = trampoline; } - private const int VarArgsSpanThreshold = 5; + private const int VarArgsSpanThreshold = 10; /// <summary> /// Calls the method represented by this <see cref="Callable"/>. @@ -92,15 +92,13 @@ namespace Godot int argc = args.Length; Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? - stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + stackalloc godot_variant.movable[VarArgsSpanThreshold] : new godot_variant.movable[argc]; - Span<IntPtr> argsSpan = argc <= 10 ? - stackalloc IntPtr[argc] : + Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ? + stackalloc IntPtr[VarArgsSpanThreshold] : new IntPtr[argc]; - using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); - fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) { @@ -128,15 +126,13 @@ namespace Godot int argc = args.Length; Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? - stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + stackalloc godot_variant.movable[VarArgsSpanThreshold] : new godot_variant.movable[argc]; - Span<IntPtr> argsSpan = argc <= 10 ? - stackalloc IntPtr[argc] : + Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ? + stackalloc IntPtr[VarArgsSpanThreshold] : new IntPtr[argc]; - using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); - fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs index 6c6a104019..ff385da1c9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs @@ -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); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index f8793332a0..cf25e1f0ae 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -344,6 +344,11 @@ namespace Godot.Collections } } + internal interface IGenericGodotDictionary + { + public Dictionary UnderlyingDictionary { get; } + } + /// <summary> /// Typed wrapper around Godot's Dictionary class, a dictionary of Variant /// typed elements allocated in the engine in C++. Useful when @@ -354,7 +359,8 @@ namespace Godot.Collections /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> public class Dictionary<[MustBeVariant] TKey, [MustBeVariant] TValue> : IDictionary<TKey, TValue>, - IReadOnlyDictionary<TKey, TValue> + IReadOnlyDictionary<TKey, TValue>, + IGenericGodotDictionary { private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) => VariantUtils.CreateFromDictionary(godotDictionary); @@ -362,49 +368,16 @@ 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; + Dictionary IGenericGodotDictionary.UnderlyingDictionary => _underlyingDict; + internal ref godot_dictionary.movable NativeValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -416,8 +389,6 @@ namespace Godot.Collections /// </summary> public Dictionary() { - ValidateVariantConversionCallbacks(); - _underlyingDict = new Dictionary(); } @@ -428,8 +399,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 +415,6 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { - ValidateVariantConversionCallbacks(); - _underlyingDict = dictionary; } @@ -481,18 +448,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 +468,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 +508,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 +518,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 +529,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 +546,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 +557,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 +570,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 +602,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 +614,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 +647,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 +659,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( diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index f2667c6807..3f9e986f62 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -282,7 +282,7 @@ namespace Godot /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by - /// the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. + /// the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. /// </summary> /// <param name="start">The start value for the interpolation.</param> /// <param name="control1">Control point that defines the bezier curve.</param> @@ -303,6 +303,27 @@ namespace Godot } /// <summary> + /// Returns the derivative at the given <paramref name="t"/> on a one dimensional Bezier curve defined by + /// the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. + /// </summary> + /// <param name="start">The start value for the interpolation.</param> + /// <param name="control1">Control point that defines the bezier curve.</param> + /// <param name="control2">Control point that defines the bezier curve.</param> + /// <param name="end">The destination value for the interpolation.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public static real_t BezierDerivative(real_t start, real_t control1, real_t control2, real_t end, real_t t) + { + // Formula from Wikipedia article on Bezier curves + real_t omt = 1 - t; + real_t omt2 = omt * omt; + real_t t2 = t * t; + + real_t d = (control1 - start) * 3 * omt2 + (control2 - control1) * 6 * omt * t + (end - control2) * 3 * t2; + return d; + } + + /// <summary> /// Converts an angle expressed in degrees to radians. /// </summary> /// <param name="deg">An angle expressed in degrees.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index 649661ee06..d2a80d0e92 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -293,6 +293,10 @@ namespace Godot.NativeInterop return VariantUtils.CreateFromDictionary(godotDictionary); case Collections.Array godotArray: return VariantUtils.CreateFromArray(godotArray); + case Collections.IGenericGodotDictionary godotDictionary: + return VariantUtils.CreateFromDictionary(godotDictionary.UnderlyingDictionary); + case Collections.IGenericGodotArray godotArray: + return VariantUtils.CreateFromArray(godotArray.UnderlyingArray); case Variant variant: return NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index b30b6a0752..c7deb6423b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -414,21 +414,6 @@ namespace Godot.NativeInterop // StringExtensions - public static partial void godotsharp_string_md5_buffer(in godot_string p_self, - out godot_packed_byte_array r_md5_buffer); - - public static partial void godotsharp_string_md5_text(in godot_string p_self, out godot_string r_md5_text); - - public static partial int godotsharp_string_rfind(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial int godotsharp_string_rfindn(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial void godotsharp_string_sha256_buffer(in godot_string p_self, - out godot_packed_byte_array r_sha256_buffer); - - public static partial void godotsharp_string_sha256_text(in godot_string p_self, - out godot_string r_sha256_text); - public static partial void godotsharp_string_simplify_path(in godot_string p_self, out godot_string r_simplified_path); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs deleted file mode 100644 index 4b3db0c01a..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs +++ /dev/null @@ -1,1057 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Godot.NativeInterop; - -// TODO: Change VariantConversionCallbacks<T>. Store the callback in a static field for quick repeated access, instead of checking every time. -internal static unsafe class VariantConversionCallbacks -{ - internal static System.Collections.Generic.Dictionary<Type, (IntPtr ToVariant, IntPtr FromVariant)> - GenericConversionCallbacks = new(); - - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - internal static delegate*<in T, godot_variant> GetToVariantCallback<T>() - { - 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 FromVector4(in Vector4 @vector4) => - VariantUtils.CreateFromVector4(@vector4); - - static godot_variant FromVector4I(in Vector4i vector4I) => - VariantUtils.CreateFromVector4i(vector4I); - - 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*<in T, godot_variant>)(delegate*<in bool, godot_variant>) - &FromBool; - } - - if (typeOfT == typeof(char)) - { - return (delegate*<in T, godot_variant>)(delegate*<in char, godot_variant>) - &FromChar; - } - - if (typeOfT == typeof(sbyte)) - { - return (delegate*<in T, godot_variant>)(delegate*<in sbyte, godot_variant>) - &FromInt8; - } - - if (typeOfT == typeof(short)) - { - return (delegate*<in T, godot_variant>)(delegate*<in short, godot_variant>) - &FromInt16; - } - - if (typeOfT == typeof(int)) - { - return (delegate*<in T, godot_variant>)(delegate*<in int, godot_variant>) - &FromInt32; - } - - if (typeOfT == typeof(long)) - { - return (delegate*<in T, godot_variant>)(delegate*<in long, godot_variant>) - &FromInt64; - } - - if (typeOfT == typeof(byte)) - { - return (delegate*<in T, godot_variant>)(delegate*<in byte, godot_variant>) - &FromUInt8; - } - - if (typeOfT == typeof(ushort)) - { - return (delegate*<in T, godot_variant>)(delegate*<in ushort, godot_variant>) - &FromUInt16; - } - - if (typeOfT == typeof(uint)) - { - return (delegate*<in T, godot_variant>)(delegate*<in uint, godot_variant>) - &FromUInt32; - } - - if (typeOfT == typeof(ulong)) - { - return (delegate*<in T, godot_variant>)(delegate*<in ulong, godot_variant>) - &FromUInt64; - } - - if (typeOfT == typeof(float)) - { - return (delegate*<in T, godot_variant>)(delegate*<in float, godot_variant>) - &FromFloat; - } - - if (typeOfT == typeof(double)) - { - return (delegate*<in T, godot_variant>)(delegate*<in double, godot_variant>) - &FromDouble; - } - - if (typeOfT == typeof(Vector2)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2, godot_variant>) - &FromVector2; - } - - if (typeOfT == typeof(Vector2i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2i, godot_variant>) - &FromVector2I; - } - - if (typeOfT == typeof(Rect2)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Rect2, godot_variant>) - &FromRect2; - } - - if (typeOfT == typeof(Rect2i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Rect2i, godot_variant>) - &FromRect2I; - } - - if (typeOfT == typeof(Transform2D)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Transform2D, godot_variant>) - &FromTransform2D; - } - - if (typeOfT == typeof(Vector3)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3, godot_variant>) - &FromVector3; - } - - if (typeOfT == typeof(Vector3i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3i, godot_variant>) - &FromVector3I; - } - - if (typeOfT == typeof(Basis)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Basis, godot_variant>) - &FromBasis; - } - - if (typeOfT == typeof(Quaternion)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Quaternion, godot_variant>) - &FromQuaternion; - } - - if (typeOfT == typeof(Transform3D)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Transform3D, godot_variant>) - &FromTransform3D; - } - - if (typeOfT == typeof(Vector4)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector4, godot_variant>) - &FromVector4; - } - - if (typeOfT == typeof(Vector4i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector4i, godot_variant>) - &FromVector4I; - } - - if (typeOfT == typeof(AABB)) - { - return (delegate*<in T, godot_variant>)(delegate*<in AABB, godot_variant>) - &FromAabb; - } - - if (typeOfT == typeof(Color)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Color, godot_variant>) - &FromColor; - } - - if (typeOfT == typeof(Plane)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Plane, godot_variant>) - &FromPlane; - } - - if (typeOfT == typeof(Callable)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Callable, godot_variant>) - &FromCallable; - } - - if (typeOfT == typeof(SignalInfo)) - { - return (delegate*<in T, godot_variant>)(delegate*<in SignalInfo, godot_variant>) - &FromSignalInfo; - } - - if (typeOfT.IsEnum) - { - var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); - - switch (Type.GetTypeCode(enumUnderlyingType)) - { - case TypeCode.SByte: - { - return (delegate*<in T, godot_variant>)(delegate*<in sbyte, godot_variant>) - &FromInt8; - } - case TypeCode.Int16: - { - return (delegate*<in T, godot_variant>)(delegate*<in short, godot_variant>) - &FromInt16; - } - case TypeCode.Int32: - { - return (delegate*<in T, godot_variant>)(delegate*<in int, godot_variant>) - &FromInt32; - } - case TypeCode.Int64: - { - return (delegate*<in T, godot_variant>)(delegate*<in long, godot_variant>) - &FromInt64; - } - case TypeCode.Byte: - { - return (delegate*<in T, godot_variant>)(delegate*<in byte, godot_variant>) - &FromUInt8; - } - case TypeCode.UInt16: - { - return (delegate*<in T, godot_variant>)(delegate*<in ushort, godot_variant>) - &FromUInt16; - } - case TypeCode.UInt32: - { - return (delegate*<in T, godot_variant>)(delegate*<in uint, godot_variant>) - &FromUInt32; - } - case TypeCode.UInt64: - { - return (delegate*<in T, godot_variant>)(delegate*<in ulong, godot_variant>) - &FromUInt64; - } - default: - return null; - } - } - - if (typeOfT == typeof(string)) - { - return (delegate*<in T, godot_variant>)(delegate*<in string, godot_variant>) - &FromString; - } - - if (typeOfT == typeof(byte[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in byte[], godot_variant>) - &FromByteArray; - } - - if (typeOfT == typeof(int[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in int[], godot_variant>) - &FromInt32Array; - } - - if (typeOfT == typeof(long[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in long[], godot_variant>) - &FromInt64Array; - } - - if (typeOfT == typeof(float[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in float[], godot_variant>) - &FromFloatArray; - } - - if (typeOfT == typeof(double[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in double[], godot_variant>) - &FromDoubleArray; - } - - if (typeOfT == typeof(string[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in string[], godot_variant>) - &FromStringArray; - } - - if (typeOfT == typeof(Vector2[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2[], godot_variant>) - &FromVector2Array; - } - - if (typeOfT == typeof(Vector3[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3[], godot_variant>) - &FromVector3Array; - } - - if (typeOfT == typeof(Color[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Color[], godot_variant>) - &FromColorArray; - } - - if (typeOfT == typeof(StringName[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in StringName[], godot_variant>) - &FromStringNameArray; - } - - if (typeOfT == typeof(NodePath[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in NodePath[], godot_variant>) - &FromNodePathArray; - } - - if (typeOfT == typeof(RID[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in RID[], godot_variant>) - &FromRidArray; - } - - if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Object, godot_variant>) - &FromGodotObject; - } - - if (typeOfT == typeof(StringName)) - { - return (delegate*<in T, godot_variant>)(delegate*<in StringName, godot_variant>) - &FromStringName; - } - - if (typeOfT == typeof(NodePath)) - { - return (delegate*<in T, godot_variant>)(delegate*<in NodePath, godot_variant>) - &FromNodePath; - } - - if (typeOfT == typeof(RID)) - { - return (delegate*<in T, godot_variant>)(delegate*<in RID, godot_variant>) - &FromRid; - } - - if (typeOfT == typeof(Godot.Collections.Dictionary)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Collections.Dictionary, godot_variant>) - &FromGodotDictionary; - } - - if (typeOfT == typeof(Godot.Collections.Array)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Collections.Array, godot_variant>) - &FromGodotArray; - } - - if (typeOfT == typeof(Variant)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Variant, godot_variant>) - &FromVariant; - } - - // TODO: - // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. - // We could make the Godot collections implement an interface and use IsAssignableFrom instead. - // Or we could just skip the check and always look for a conversion callback for the type. - if (typeOfT.IsGenericType) - { - var genericTypeDef = typeOfT.GetGenericTypeDefinition(); - - if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || - genericTypeDef == typeof(Godot.Collections.Array<>)) - { - RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); - - if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) - { - return (delegate*<in T, godot_variant>)genericConversion.ToVariant; - } - } - } - - return null; - } - - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - internal static delegate*<in godot_variant, T> GetToManagedCallback<T>() - { - 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 Vector4 ToVector4(in godot_variant variant) => - VariantUtils.ConvertToVector4(variant); - - static Vector4i ToVector4I(in godot_variant variant) => - VariantUtils.ConvertToVector4i(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*<in godot_variant, T>)(delegate*<in godot_variant, bool>) - &ToBool; - } - - if (typeOfT == typeof(char)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, char>) - &ToChar; - } - - if (typeOfT == typeof(sbyte)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, sbyte>) - &ToInt8; - } - - if (typeOfT == typeof(short)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, short>) - &ToInt16; - } - - if (typeOfT == typeof(int)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int>) - &ToInt32; - } - - if (typeOfT == typeof(long)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long>) - &ToInt64; - } - - if (typeOfT == typeof(byte)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte>) - &ToUInt8; - } - - if (typeOfT == typeof(ushort)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ushort>) - &ToUInt16; - } - - if (typeOfT == typeof(uint)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, uint>) - &ToUInt32; - } - - if (typeOfT == typeof(ulong)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ulong>) - &ToUInt64; - } - - if (typeOfT == typeof(float)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, float>) - &ToFloat; - } - - if (typeOfT == typeof(double)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, double>) - &ToDouble; - } - - if (typeOfT == typeof(Vector2)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2>) - &ToVector2; - } - - if (typeOfT == typeof(Vector2i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2i>) - &ToVector2I; - } - - if (typeOfT == typeof(Rect2)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Rect2>) - &ToRect2; - } - - if (typeOfT == typeof(Rect2i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Rect2i>) - &ToRect2I; - } - - if (typeOfT == typeof(Transform2D)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Transform2D>) - &ToTransform2D; - } - - if (typeOfT == typeof(Vector3)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3>) - &ToVector3; - } - - if (typeOfT == typeof(Vector3i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3i>) - &ToVector3I; - } - - if (typeOfT == typeof(Basis)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Basis>) - &ToBasis; - } - - if (typeOfT == typeof(Quaternion)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Quaternion>) - &ToQuaternion; - } - - if (typeOfT == typeof(Transform3D)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Transform3D>) - &ToTransform3D; - } - - if (typeOfT == typeof(Vector4)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector4>) - &ToVector4; - } - - if (typeOfT == typeof(Vector4i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector4i>) - &ToVector4I; - } - - if (typeOfT == typeof(AABB)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, AABB>) - &ToAabb; - } - - if (typeOfT == typeof(Color)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Color>) - &ToColor; - } - - if (typeOfT == typeof(Plane)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Plane>) - &ToPlane; - } - - if (typeOfT == typeof(Callable)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Callable>) - &ToCallable; - } - - if (typeOfT == typeof(SignalInfo)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, SignalInfo>) - &ToSignalInfo; - } - - if (typeOfT.IsEnum) - { - var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); - - switch (Type.GetTypeCode(enumUnderlyingType)) - { - case TypeCode.SByte: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, sbyte>) - &ToInt8; - } - case TypeCode.Int16: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, short>) - &ToInt16; - } - case TypeCode.Int32: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int>) - &ToInt32; - } - case TypeCode.Int64: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long>) - &ToInt64; - } - case TypeCode.Byte: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte>) - &ToUInt8; - } - case TypeCode.UInt16: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ushort>) - &ToUInt16; - } - case TypeCode.UInt32: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, uint>) - &ToUInt32; - } - case TypeCode.UInt64: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ulong>) - &ToUInt64; - } - default: - return null; - } - } - - if (typeOfT == typeof(string)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, string>) - &ToString; - } - - if (typeOfT == typeof(byte[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte[]>) - &ToByteArray; - } - - if (typeOfT == typeof(int[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int[]>) - &ToInt32Array; - } - - if (typeOfT == typeof(long[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long[]>) - &ToInt64Array; - } - - if (typeOfT == typeof(float[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, float[]>) - &ToFloatArray; - } - - if (typeOfT == typeof(double[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, double[]>) - &ToDoubleArray; - } - - if (typeOfT == typeof(string[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, string[]>) - &ToStringArray; - } - - if (typeOfT == typeof(Vector2[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2[]>) - &ToVector2Array; - } - - if (typeOfT == typeof(Vector3[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3[]>) - &ToVector3Array; - } - - if (typeOfT == typeof(Color[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Color[]>) - &ToColorArray; - } - - if (typeOfT == typeof(StringName[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, StringName[]>) - &ToStringNameArray; - } - - if (typeOfT == typeof(NodePath[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, NodePath[]>) - &ToNodePathArray; - } - - if (typeOfT == typeof(RID[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, RID[]>) - &ToRidArray; - } - - if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Object>) - &ToGodotObject; - } - - if (typeOfT == typeof(StringName)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, StringName>) - &ToStringName; - } - - if (typeOfT == typeof(NodePath)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, NodePath>) - &ToNodePath; - } - - if (typeOfT == typeof(RID)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, RID>) - &ToRid; - } - - if (typeOfT == typeof(Godot.Collections.Dictionary)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Collections.Dictionary>) - &ToGodotDictionary; - } - - if (typeOfT == typeof(Godot.Collections.Array)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Collections.Array>) - &ToGodotArray; - } - - if (typeOfT == typeof(Variant)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Variant>) - &ToVariant; - } - - // TODO: - // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. - // We could make the Godot collections implement an interface and use IsAssignableFrom instead. - // Or we could just skip the check and always look for a conversion callback for the type. - if (typeOfT.IsGenericType) - { - var genericTypeDef = typeOfT.GetGenericTypeDefinition(); - - if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || - genericTypeDef == typeof(Godot.Collections.Array<>)) - { - RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); - - if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) - { - return (delegate*<in godot_variant, T>)genericConversion.FromVariant; - } - } - } - - // ReSharper restore RedundantCast - - return null; - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs deleted file mode 100644 index 46f31bbf4e..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Godot.NativeInterop -{ - internal readonly ref struct VariantSpanDisposer - { - private readonly Span<godot_variant.movable> _variantSpan; - - // IMPORTANT: The span element must be default initialized. - // Make sure call Clear() on the span if it was created with stackalloc. - public VariantSpanDisposer(Span<godot_variant.movable> variantSpan) - { - _variantSpan = variantSpan; - } - - public void Dispose() - { - for (int i = 0; i < _variantSpan.Length; i++) - _variantSpan[i].DangerousSelfRef.Dispose(); - } - } - - internal static class VariantSpanExtensions - { - // Used to make sure we always initialize the span values to the default, - // as we need that in order to safely dispose all elements after. - public static Span<godot_variant.movable> Cleared(this Span<godot_variant.movable> span) - { - span.Clear(); - return span; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs index 57f9ec7d95..ba8e7a6c65 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -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 }; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs new file mode 100644 index 0000000000..694da6db77 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs @@ -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>() => 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); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index 371729ebec..8b1b73fcc3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -3,6 +3,14 @@ using System.Runtime.InteropServices; namespace Godot { + /// <summary> + /// A 4x4 matrix used for 3D projective transformations. It can represent transformations such as + /// translation, rotation, scaling, shearing, and perspective division. It consists of four + /// <see cref="Vector4"/> columns. + /// For purely linear transformations (translation, rotation, and scale), it is recommended to use + /// <see cref="Transform3D"/>, as it is more performant and has a lower memory footprint. + /// Used internally as <see cref="Camera3D"/>'s projection matrix. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Projection : IEquatable<Projection> @@ -59,48 +67,107 @@ namespace Godot public Vector4 w; /// <summary> - /// Constructs a projection from 4 vectors (matrix columns). + /// Access whole columns in the form of <see cref="Vector4"/>. /// </summary> - /// <param name="x">The X column, or column index 0.</param> - /// <param name="y">The Y column, or column index 1.</param> - /// <param name="z">The Z column, or column index 2.</param> - /// <param name="w">The W column, or column index 3.</param> - public Projection(Vector4 x, Vector4 y, Vector4 z, Vector4 w) + /// <param name="column">Which column vector.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="column"/> is not 0, 1, 2 or 3. + /// </exception> + public Vector4 this[int column] { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + readonly get + { + switch (column) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } + set + { + switch (column) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + case 3: + w = value; + return; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } } /// <summary> - /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>. + /// Access single values. /// </summary> - /// <param name="transform">The <see cref="Transform3D"/>.</param> - public Projection(Transform3D transform) + /// <param name="column">Which column vector.</param> + /// <param name="row">Which row of the column.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="column"/> or <paramref name="row"/> are not 0, 1, 2 or 3. + /// </exception> + public real_t this[int column, int row] { - x = new Vector4(transform.basis.Row0.x, transform.basis.Row1.x, transform.basis.Row2.x, 0); - y = new Vector4(transform.basis.Row0.y, transform.basis.Row1.y, transform.basis.Row2.y, 0); - z = new Vector4(transform.basis.Row0.z, transform.basis.Row1.z, transform.basis.Row2.z, 0); - w = new Vector4(transform.origin.x, transform.origin.y, transform.origin.z, 1); + readonly get + { + switch (column) + { + case 0: + return x[row]; + case 1: + return y[row]; + case 2: + return z[row]; + case 3: + return w[row]; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } + set + { + switch (column) + { + case 0: + x[row] = value; + return; + case 1: + y[row] = value; + return; + case 2: + z[row] = value; + return; + case 3: + w[row] = value; + return; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } } /// <summary> - /// Constructs a new <see cref="Transform3D"/> from the <see cref="Projection"/>. + /// Creates a new <see cref="Projection"/> that projects positions from a depth range of + /// <c>-1</c> to <c>1</c> to one that ranges from <c>0</c> to <c>1</c>, and flips the projected + /// positions vertically, according to <paramref name="flipY"/>. /// </summary> - /// <param name="proj">The <see cref="Projection"/>.</param> - public static explicit operator Transform3D(Projection proj) - { - return new Transform3D( - new Basis( - new Vector3(proj.x.x, proj.x.y, proj.x.z), - new Vector3(proj.y.x, proj.y.y, proj.y.z), - new Vector3(proj.z.x, proj.z.y, proj.z.z) - ), - new Vector3(proj.w.x, proj.w.y, proj.w.z) - ); - } - + /// <param name="flipY">If the projection should be flipped vertically.</param> + /// <returns>The created projection.</returns> public static Projection CreateDepthCorrection(bool flipY) { return new Projection( @@ -111,6 +178,12 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that scales a given projection to fit around + /// a given <see cref="AABB"/> in projection space. + /// </summary> + /// <param name="aabb">The AABB to fit the projection around.</param> + /// <returns>The created projection.</returns> public static Projection CreateFitAabb(AABB aabb) { Vector3 min = aabb.Position; @@ -124,6 +197,25 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> for projecting positions onto a head-mounted display with + /// the given X:Y aspect ratio, distance between eyes, display width, distance to lens, oversampling factor, + /// and depth clipping planes. + /// <paramref name="eye"/> creates the projection for the left eye when set to 1, + /// or the right eye when set to 2. + /// </summary> + /// <param name="eye"> + /// The eye to create the projection for. + /// The left eye when set to 1, the right eye when set to 2. + /// </param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="intraocularDist">The distance between the eyes.</param> + /// <param name="displayWidth">The display width.</param> + /// <param name="displayToLens">The distance to the lens.</param> + /// <param name="oversample">The oversampling factor.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateForHmd(int eye, real_t aspect, real_t intraocularDist, real_t displayWidth, real_t displayToLens, real_t oversample, real_t zNear, real_t zFar) { real_t f1 = (intraocularDist * (real_t)0.5) / displayToLens; @@ -148,6 +240,17 @@ namespace Godot } } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions in a frustum with + /// the given clipping planes. + /// </summary> + /// <param name="left">The left clipping distance.</param> + /// <param name="right">The right clipping distance.</param> + /// <param name="bottom">The bottom clipping distance.</param> + /// <param name="top">The top clipping distance.</param> + /// <param name="near">The near clipping distance.</param> + /// <param name="far">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateFrustum(real_t left, real_t right, real_t bottom, real_t top, real_t near, real_t far) { if (right <= left) @@ -179,6 +282,18 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions in a frustum with + /// the given size, X:Y aspect ratio, offset, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="size">The frustum size.</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="offset">The offset to apply.</param> + /// <param name="near">The near clipping distance.</param> + /// <param name="far">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreateFrustumAspect(real_t size, real_t aspect, Vector2 offset, real_t near, real_t far, bool flipFov) { if (!flipFov) @@ -188,6 +303,11 @@ namespace Godot return CreateFrustum(-size / 2 + offset.x, +size / 2 + offset.x, -size / aspect / 2 + offset.y, +size / aspect / 2 + offset.y, near, far); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions into the given <see cref="Rect2"/>. + /// </summary> + /// <param name="rect">The Rect2 to project positions into.</param> + /// <returns>The created projection.</returns> public static Projection CreateLightAtlasRect(Rect2 rect) { return new Projection( @@ -198,6 +318,17 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with + /// the given clipping planes. + /// </summary> + /// <param name="left">The left clipping distance.</param> + /// <param name="right">The right clipping distance.</param> + /// <param name="bottom">The bottom clipping distance.</param> + /// <param name="top">The top clipping distance.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateOrthogonal(real_t left, real_t right, real_t bottom, real_t top, real_t zNear, real_t zFar) { Projection proj = Projection.Identity; @@ -211,6 +342,17 @@ namespace Godot return proj; } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with + /// the given size, X:Y aspect ratio, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="size">The frustum size.</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreateOrthogonalAspect(real_t size, real_t aspect, real_t zNear, real_t zFar, bool flipFov) { if (!flipFov) @@ -220,6 +362,17 @@ namespace Godot return CreateOrthogonal(-size / 2, +size / 2, -size / aspect / 2, +size / aspect / 2, zNear, zFar); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with + /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="fovyDegrees">The vertical field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreatePerspective(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov) { if (flipFov) @@ -249,6 +402,27 @@ namespace Godot return proj; } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with + /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping distances. + /// The projection is adjusted for a head-mounted display with the given distance between eyes and distance + /// to a point that can be focused on. + /// <paramref name="eye"/> creates the projection for the left eye when set to 1, + /// or the right eye when set to 2. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="fovyDegrees">The vertical field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <param name="eye"> + /// The eye to create the projection for. + /// The left eye when set to 1, the right eye when set to 2. + /// </param> + /// <param name="intraocularDist">The distance between the eyes.</param> + /// <param name="convergenceDist">The distance to a point of convergence that can be focused on.</param> + /// <returns>The created projection.</returns> public static Projection CreatePerspectiveHmd(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov, int eye, real_t intraocularDist, real_t convergenceDist) { if (flipFov) @@ -286,6 +460,13 @@ namespace Godot return proj * cm; } + /// <summary> + /// Returns a scalar value that is the signed factor by which areas are scaled by this matrix. + /// If the sign is negative, the matrix flips the orientation of the area. + /// The determinant can be used to calculate the invertibility of a matrix or solve linear systems + /// of equations involving the matrix, among other applications. + /// </summary> + /// <returns>The determinant calculated from this projection.</returns> public readonly real_t Determinant() { return x.w * y.z * z.y * w.x - x.z * y.w * z.y * w.x - @@ -302,12 +483,20 @@ namespace Godot x.y * y.x * z.z * w.w + x.x * y.y * z.z * w.w; } + /// <summary> + /// Returns the X:Y aspect ratio of this <see cref="Projection"/>'s viewport. + /// </summary> + /// <returns>The aspect ratio from this projection's viewport.</returns> public readonly real_t GetAspect() { Vector2 vpHe = GetViewportHalfExtents(); return vpHe.x / vpHe.y; } + /// <summary> + /// Returns the horizontal field of view of the projection (in degrees). + /// </summary> + /// <returns>The horizontal field of view of this projection.</returns> public readonly real_t GetFov() { Plane rightPlane = new Plane(x.w - x.x, y.w - y.x, z.w - z.x, -w.w + w.x).Normalized(); @@ -322,11 +511,22 @@ namespace Godot } } + /// <summary> + /// Returns the vertical field of view of the projection (in degrees) associated with + /// the given horizontal field of view (in degrees) and aspect ratio. + /// </summary> + /// <param name="fovx">The horizontal field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <returns>The vertical field of view of this projection.</returns> public static real_t GetFovy(real_t fovx, real_t aspect) { return Mathf.RadToDeg(Mathf.Atan(aspect * Mathf.Tan(Mathf.DegToRad(fovx) * (real_t)0.5)) * (real_t)2.0); } + /// <summary> + /// Returns the factor by which the visible level of detail is scaled by this <see cref="Projection"/>. + /// </summary> + /// <returns>The level of detail factor for this projection.</returns> public readonly real_t GetLodMultiplier() { if (IsOrthogonal()) @@ -341,6 +541,12 @@ namespace Godot } } + /// <summary> + /// Returns the number of pixels with the given pixel width displayed per meter, after + /// this <see cref="Projection"/> is applied. + /// </summary> + /// <param name="forPixelWidth">The width for each pixel (in meters).</param> + /// <returns>The number of pixels per meter.</returns> public readonly int GetPixelsPerMeter(int forPixelWidth) { Vector3 result = this * new Vector3(1, 0, -1); @@ -348,6 +554,15 @@ namespace Godot return (int)((result.x * (real_t)0.5 + (real_t)0.5) * forPixelWidth); } + /// <summary> + /// Returns the clipping plane of this <see cref="Projection"/> whose index is given + /// by <paramref name="plane"/>. + /// <paramref name="plane"/> should be equal to one of <see cref="Planes.Near"/>, + /// <see cref="Planes.Far"/>, <see cref="Planes.Left"/>, <see cref="Planes.Top"/>, + /// <see cref="Planes.Right"/>, or <see cref="Planes.Bottom"/>. + /// </summary> + /// <param name="plane">The kind of clipping plane to get from the projection.</param> + /// <returns>The clipping plane of this projection.</returns> public readonly Plane GetProjectionPlane(Planes plane) { Plane newPlane = plane switch @@ -364,28 +579,49 @@ namespace Godot return newPlane.Normalized(); } + /// <summary> + /// Returns the dimensions of the far clipping plane of the projection, divided by two. + /// </summary> + /// <returns>The half extents for this projection's far plane.</returns> public readonly Vector2 GetFarPlaneHalfExtents() { var res = GetProjectionPlane(Planes.Far).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top)); return new Vector2(res.Value.x, res.Value.y); } + /// <summary> + /// Returns the dimensions of the viewport plane that this <see cref="Projection"/> + /// projects positions onto, divided by two. + /// </summary> + /// <returns>The half extents for this projection's viewport plane.</returns> public readonly Vector2 GetViewportHalfExtents() { var res = GetProjectionPlane(Planes.Near).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top)); return new Vector2(res.Value.x, res.Value.y); } + /// <summary> + /// Returns the distance for this <see cref="Projection"/> beyond which positions are clipped. + /// </summary> + /// <returns>The distance beyond which positions are clipped.</returns> public readonly real_t GetZFar() { return GetProjectionPlane(Planes.Far).D; } + /// <summary> + /// Returns the distance for this <see cref="Projection"/> before which positions are clipped. + /// </summary> + /// <returns>The distance before which positions are clipped.</returns> public readonly real_t GetZNear() { return -GetProjectionPlane(Planes.Near).D; } + /// <summary> + /// Returns a copy of this <see cref="Projection"/> with the signs of the values of the Y column flipped. + /// </summary> + /// <returns>The flipped projection.</returns> public readonly Projection FlippedY() { Projection proj = this; @@ -393,6 +629,13 @@ namespace Godot return proj; } + /// <summary> + /// Returns a <see cref="Projection"/> with the near clipping distance adjusted to be + /// <paramref name="newZNear"/>. + /// Note: The original <see cref="Projection"/> must be a perspective projection. + /// </summary> + /// <param name="newZNear">The near clipping distance to adjust the projection to.</param> + /// <returns>The adjusted projection.</returns> public readonly Projection PerspectiveZNearAdjusted(real_t newZNear) { Projection proj = this; @@ -404,6 +647,12 @@ namespace Godot return proj; } + /// <summary> + /// Returns a <see cref="Projection"/> with the X and Y values from the given <see cref="Vector2"/> + /// added to the first and second values of the final column respectively. + /// </summary> + /// <param name="offset">The offset to apply to the projection.</param> + /// <returns>The offseted projection.</returns> public readonly Projection JitterOffseted(Vector2 offset) { Projection proj = this; @@ -412,6 +661,11 @@ namespace Godot return proj; } + /// <summary> + /// Returns a <see cref="Projection"/> that performs the inverse of this <see cref="Projection"/>'s + /// projective transformation. + /// </summary> + /// <returns>The inverted projection.</returns> public readonly Projection Inverse() { Projection proj = this; @@ -535,11 +789,70 @@ namespace Godot return proj; } + /// <summary> + /// Returns <see langword="true"/> if this <see cref="Projection"/> performs an orthogonal projection. + /// </summary> + /// <returns>If the projection performs an orthogonal projection.</returns> public readonly bool IsOrthogonal() { return w.w == (real_t)1.0; } + // Constants + private static readonly Projection _zero = new Projection( + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0) + ); + private static readonly Projection _identity = new Projection( + new Vector4(1, 0, 0, 0), + new Vector4(0, 1, 0, 0), + new Vector4(0, 0, 1, 0), + new Vector4(0, 0, 0, 1) + ); + + /// <summary> + /// Zero projection, a projection with all components set to <c>0</c>. + /// </summary> + /// <value>Equivalent to <c>new Projection(Vector4.Zero, Vector4.Zero, Vector4.Zero, Vector4.Zero)</c>.</value> + public static Projection Zero { get { return _zero; } } + + /// <summary> + /// The identity projection, with no distortion applied. + /// This is used as a replacement for <c>Projection()</c> in GDScript. + /// Do not use <c>new Projection()</c> with no arguments in C#, because it sets all values to zero. + /// </summary> + /// <value>Equivalent to <c>new Projection(new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1))</c>.</value> + public static Projection Identity { get { return _identity; } } + + /// <summary> + /// Constructs a projection from 4 vectors (matrix columns). + /// </summary> + /// <param name="x">The X column, or column index 0.</param> + /// <param name="y">The Y column, or column index 1.</param> + /// <param name="z">The Z column, or column index 2.</param> + /// <param name="w">The W column, or column index 3.</param> + public Projection(Vector4 x, Vector4 y, Vector4 z, Vector4 w) + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /// <summary> + /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>. + /// </summary> + /// <param name="transform">The <see cref="Transform3D"/>.</param> + public Projection(Transform3D transform) + { + x = new Vector4(transform.basis.Row0.x, transform.basis.Row1.x, transform.basis.Row2.x, 0); + y = new Vector4(transform.basis.Row0.y, transform.basis.Row1.y, transform.basis.Row2.y, 0); + z = new Vector4(transform.basis.Row0.z, transform.basis.Row1.z, transform.basis.Row2.z, 0); + w = new Vector4(transform.origin.x, transform.origin.y, transform.origin.z, 1); + } + /// <summary> /// Composes these two projections by multiplying them /// together. This has the effect of applying the right @@ -646,127 +959,41 @@ namespace Godot } /// <summary> - /// Access whole columns in the form of <see cref="Vector4"/>. + /// Constructs a new <see cref="Transform3D"/> from the <see cref="Projection"/>. /// </summary> - /// <param name="column">Which column vector.</param> - /// <exception cref="ArgumentOutOfRangeException"> - /// <paramref name="column"/> is not 0, 1, 2 or 3. - /// </exception> - public Vector4 this[int column] + /// <param name="proj">The <see cref="Projection"/>.</param> + public static explicit operator Transform3D(Projection proj) { - readonly get - { - switch (column) - { - case 0: - return x; - case 1: - return y; - case 2: - return z; - case 3: - return w; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } - set - { - switch (column) - { - case 0: - x = value; - return; - case 1: - y = value; - return; - case 2: - z = value; - return; - case 3: - w = value; - return; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } + return new Transform3D( + new Basis( + new Vector3(proj.x.x, proj.x.y, proj.x.z), + new Vector3(proj.y.x, proj.y.y, proj.y.z), + new Vector3(proj.z.x, proj.z.y, proj.z.z) + ), + new Vector3(proj.w.x, proj.w.y, proj.w.z) + ); } /// <summary> - /// Access single values. + /// Returns <see langword="true"/> if the projection is exactly equal + /// to the given object (<see paramref="obj"/>). /// </summary> - /// <param name="column">Which column vector.</param> - /// <param name="row">Which row of the column.</param> - /// <exception cref="ArgumentOutOfRangeException"> - /// <paramref name="column"/> or <paramref name="row"/> are not 0, 1, 2 or 3. - /// </exception> - public real_t this[int column, int row] + /// <param name="obj">The object to compare with.</param> + /// <returns>Whether or not the vector and the object are equal.</returns> + public override readonly bool Equals(object obj) { - readonly get - { - switch (column) - { - case 0: - return x[row]; - case 1: - return y[row]; - case 2: - return z[row]; - case 3: - return w[row]; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } - set - { - switch (column) - { - case 0: - x[row] = value; - return; - case 1: - y[row] = value; - return; - case 2: - z[row] = value; - return; - case 3: - w[row] = value; - return; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } + return obj is Projection other && Equals(other); } - // Constants - private static readonly Projection _zero = new Projection( - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0) - ); - private static readonly Projection _identity = new Projection( - new Vector4(1, 0, 0, 0), - new Vector4(0, 1, 0, 0), - new Vector4(0, 0, 1, 0), - new Vector4(0, 0, 0, 1) - ); - - /// <summary> - /// Zero projection, a projection with all components set to <c>0</c>. - /// </summary> - /// <value>Equivalent to <c>new Projection(Vector4.Zero, Vector4.Zero, Vector4.Zero, Vector4.Zero)</c>.</value> - public static Projection Zero { get { return _zero; } } - /// <summary> - /// The identity projection, with no distortion applied. - /// This is used as a replacement for <c>Projection()</c> in GDScript. - /// Do not use <c>new Projection()</c> with no arguments in C#, because it sets all values to zero. + /// Returns <see langword="true"/> if the projections are exactly equal. /// </summary> - /// <value>Equivalent to <c>new Projection(new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1))</c>.</value> - public static Projection Identity { get { return _identity; } } + /// <param name="other">The other projection.</param> + /// <returns>Whether or not the projections are exactly equal.</returns> + public readonly bool Equals(Projection other) + { + return x == other.x && y == other.y && z == other.z && w == other.w; + } /// <summary> /// Serves as the hash function for <see cref="Projection"/>. @@ -797,26 +1024,5 @@ namespace Godot $"{z.x.ToString(format)}, {z.y.ToString(format)}, {z.z.ToString(format)}, {z.w.ToString(format)}\n" + $"{w.x.ToString(format)}, {w.y.ToString(format)}, {w.z.ToString(format)}, {w.w.ToString(format)}\n"; } - - /// <summary> - /// Returns <see langword="true"/> if the projection is exactly equal - /// to the given object (<see paramref="obj"/>). - /// </summary> - /// <param name="obj">The object to compare with.</param> - /// <returns>Whether or not the vector and the object are equal.</returns> - public override readonly bool Equals(object obj) - { - return obj is Projection other && Equals(other); - } - - /// <summary> - /// Returns <see langword="true"/> if the projections are exactly equal. - /// </summary> - /// <param name="other">The other projection.</param> - /// <returns>Whether or not the projections are exactly equal.</returns> - public readonly bool Equals(Projection other) - { - return x == other.x && y == other.y && z == other.z && w == other.w; - } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index f511233fcc..d4329d78c1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Security; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Godot.NativeInterop; @@ -67,30 +69,13 @@ namespace Godot } /// <summary> - /// If the string is a path to a file, return the path to the file without the extension. - /// </summary> - /// <seealso cref="GetExtension(string)"/> - /// <seealso cref="GetBaseDir(string)"/> - /// <seealso cref="GetFile(string)"/> - /// <param name="instance">The path to a file.</param> - /// <returns>The path to the file without the extension.</returns> - public static string GetBaseName(this string instance) - { - int index = instance.LastIndexOf('.'); - - if (index > 0) - return instance.Substring(0, index); - - return instance; - } - - /// <summary> /// Returns <see langword="true"/> if the strings begins /// with the given string <paramref name="text"/>. /// </summary> /// <param name="instance">The string to check.</param> /// <param name="text">The beginning string.</param> /// <returns>If the string begins with the given string.</returns> + [Obsolete("Use string.StartsWith instead.")] public static bool BeginsWith(this string instance, string text) { return instance.StartsWith(text); @@ -144,15 +129,15 @@ namespace Godot } /// <summary> - /// Returns the amount of substrings <paramref name="what"/> in the string. + /// Returns the number of occurrences of substring <paramref name="what"/> in the string. /// </summary> /// <param name="instance">The string where the substring will be searched.</param> /// <param name="what">The substring that will be counted.</param> - /// <param name="caseSensitive">If the search is case sensitive.</param> /// <param name="from">Index to start searching from.</param> /// <param name="to">Index to stop searching at.</param> - /// <returns>Amount of substrings in the string.</returns> - public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) + /// <param name="caseSensitive">If the search is case sensitive.</param> + /// <returns>Number of occurrences of the substring in the string.</returns> + public static int Count(this string instance, string what, int from = 0, int to = 0, bool caseSensitive = true) { if (what.Length == 0) { @@ -211,6 +196,82 @@ namespace Godot } /// <summary> + /// Returns the number of occurrences of substring <paramref name="what"/> (ignoring case) + /// between <paramref name="from"/> and <paramref name="to"/> positions. If <paramref name="from"/> + /// and <paramref name="to"/> equals 0 the whole string will be used. If only <paramref name="to"/> + /// equals 0 the remained substring will be used. + /// </summary> + /// <param name="instance">The string where the substring will be searched.</param> + /// <param name="what">The substring that will be counted.</param> + /// <param name="from">Index to start searching from.</param> + /// <param name="to">Index to stop searching at.</param> + /// <returns>Number of occurrences of the substring in the string.</returns> + public static int CountN(this string instance, string what, int from = 0, int to = 0) + { + return instance.Count(what, from, to, caseSensitive: false); + } + + /// <summary> + /// Returns a copy of the string with indentation (leading tabs and spaces) removed. + /// See also <see cref="Indent"/> to add indentation. + /// </summary> + /// <param name="instance">The string to remove the indentation from.</param> + /// <returns>The string with the indentation removed.</returns> + public static string Dedent(this string instance) + { + var sb = new StringBuilder(); + string indent = ""; + bool hasIndent = false; + bool hasText = false; + int lineStart = 0; + int indentStop = -1; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (hasText) + { + sb.Append(instance.Substring(indentStop, i - indentStop)); + } + sb.Append('\n'); + hasText = false; + lineStart = i + 1; + indentStop = -1; + } + else if (!hasText) + { + if (c > 32) + { + hasText = true; + if (!hasIndent) + { + hasIndent = true; + indent = instance.Substring(lineStart, i - lineStart); + indentStop = i; + } + } + if (hasIndent && indentStop < 0) + { + int j = i - lineStart; + if (j >= indent.Length || c != indent[j]) + { + indentStop = i; + } + } + } + } + + if (hasText) + { + sb.Append(instance.Substring(indentStop, instance.Length - indentStop)); + } + + return sb.ToString(); + } + + /// <summary> /// Returns a copy of the string with special characters escaped using the C language standard. /// </summary> /// <param name="instance">The string to escape.</param> @@ -443,29 +504,6 @@ namespace Godot } /// <summary> - /// Returns <see langword="true"/> if the strings ends - /// with the given string <paramref name="text"/>. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <param name="text">The ending string.</param> - /// <returns>If the string ends with the given string.</returns> - public static bool EndsWith(this string instance, string text) - { - return instance.EndsWith(text); - } - - /// <summary> - /// Erase <paramref name="chars"/> characters from the string starting from <paramref name="pos"/>. - /// </summary> - /// <param name="instance">The string to modify.</param> - /// <param name="pos">Starting position from which to erase.</param> - /// <param name="chars">Amount of characters to erase.</param> - public static void Erase(this StringBuilder instance, int pos, int chars) - { - instance.Remove(pos, chars); - } - - /// <summary> /// Returns the extension without the leading period character (<c>.</c>) /// if the string is a valid file name or path. If the string does not contain /// an extension, returns an empty string instead. @@ -489,7 +527,7 @@ namespace Godot /// <returns>The extension of the file or an empty string.</returns> public static string GetExtension(this string instance) { - int pos = instance.FindLast("."); + int pos = instance.RFind("."); if (pos < 0) return instance; @@ -498,12 +536,16 @@ namespace Godot } /// <summary> - /// Find the first occurrence of a substring. Optionally, the search starting position can be passed. + /// Returns the index of the first occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing + /// to the end of the string. + /// Note: If you just want to know whether a string contains a substring, use the + /// <see cref="string.Contains(string)"/> method. /// </summary> /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -519,9 +561,9 @@ namespace Godot /// Find the first occurrence of a char. Optionally, the search starting position can be passed. /// </summary> /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -529,50 +571,21 @@ namespace Godot /// <returns>The first instance of the char, or -1 if not found.</returns> public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true) { - // TODO: Could be more efficient if we get a char version of `IndexOf`. - // See https://github.com/dotnet/runtime/issues/44116 - return instance.IndexOf(what.ToString(), from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); - } + if (caseSensitive) + return instance.IndexOf(what, from); - /// <summary>Find the last occurrence of a substring.</summary> - /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> - /// <seealso cref="FindN(string, string, int)"/> - /// <param name="instance">The string that will be searched.</param> - /// <param name="what">The substring to find.</param> - /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> - /// <returns>The starting position of the substring, or -1 if not found.</returns> - public static int FindLast(this string instance, string what, bool caseSensitive = true) - { - return instance.FindLast(what, instance.Length - 1, caseSensitive); - } - - /// <summary>Find the last occurrence of a substring specifying the search starting position.</summary> - /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindN(string, string, int)"/> - /// <param name="instance">The string that will be searched.</param> - /// <param name="what">The substring to find.</param> - /// <param name="from">The search starting position.</param> - /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> - /// <returns>The starting position of the substring, or -1 if not found.</returns> - public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) - { - return instance.LastIndexOf(what, from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return CultureInfo.InvariantCulture.CompareInfo.IndexOf(instance, what, from, CompareOptions.OrdinalIgnoreCase); } /// <summary> - /// Find the first occurrence of a substring but search as case-insensitive. - /// Optionally, the search starting position can be passed. + /// Returns the index of the first case-insensitive occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing + /// to the end of the string. /// </summary> /// <seealso cref="Find(string, string, int, bool)"/> /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -616,7 +629,7 @@ namespace Godot } } - int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\")); + int sep = Mathf.Max(rs.RFind("/"), rs.RFind("\\")); if (sep == -1) return directory; @@ -625,6 +638,24 @@ namespace Godot } /// <summary> + /// If the string is a path to a file, return the path to the file without the extension. + /// </summary> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The path to the file without the extension.</returns> + public static string GetBaseName(this string instance) + { + int index = instance.RFind("."); + + if (index > 0) + return instance.Substring(0, index); + + return instance; + } + + /// <summary> /// If the string is a path to a file, return the file and ignore the base directory. /// </summary> /// <seealso cref="GetBaseName(string)"/> @@ -634,7 +665,7 @@ namespace Godot /// <returns>The file name.</returns> public static string GetFile(this string instance) { - int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\")); + int sep = Mathf.Max(instance.RFind("/"), instance.RFind("\\")); if (sep == -1) return instance; @@ -643,8 +674,8 @@ namespace Godot } /// <summary> - /// Converts the given byte array of ASCII encoded text to a string. - /// Faster alternative to <see cref="GetStringFromUTF8"/> if the + /// Converts ASCII encoded array to string. + /// Fast alternative to <see cref="GetStringFromUTF8"/> if the /// content is ASCII-only. Unlike the UTF-8 function this function /// maps every byte to a character in the array. Multibyte sequences /// will not be interpreted correctly. For parsing user input always @@ -658,13 +689,35 @@ namespace Godot } /// <summary> - /// Converts the given byte array of UTF-8 encoded text to a string. + /// Converts UTF-16 encoded array to string using the little endian byte order. + /// </summary> + /// <param name="bytes">A byte array of UTF-16 characters.</param> + /// <returns>A string created from the bytes.</returns> + public static string GetStringFromUTF16(this byte[] bytes) + { + return Encoding.Unicode.GetString(bytes); + } + + /// <summary> + /// Converts UTF-32 encoded array to string using the little endian byte order. + /// </summary> + /// <param name="bytes">A byte array of UTF-32 characters.</param> + /// <returns>A string created from the bytes.</returns> + public static string GetStringFromUTF32(this byte[] bytes) + { + return Encoding.UTF32.GetString(bytes); + } + + /// <summary> + /// Converts UTF-8 encoded array to string. /// Slower than <see cref="GetStringFromASCII"/> but supports UTF-8 /// encoded data. Use this function if you are unsure about the /// source of the data. For user input this function /// should always be preferred. /// </summary> - /// <param name="bytes">A byte array of UTF-8 characters (a character may take up multiple bytes).</param> + /// <param name="bytes"> + /// A byte array of UTF-8 characters (a character may take up multiple bytes). + /// </param> /// <returns>A string created from the bytes.</returns> public static string GetStringFromUTF8(this byte[] bytes) { @@ -766,18 +819,44 @@ namespace Godot } /// <summary> - /// Inserts a substring at a given position. + /// Returns a copy of the string with lines indented with <paramref name="prefix"/>. + /// For example, the string can be indented with two tabs using <c>"\t\t"</c>, + /// or four spaces using <c>" "</c>. The prefix can be any string so it can + /// also be used to comment out strings with e.g. <c>"// </c>. + /// See also <see cref="Dedent"/> to remove indentation. + /// Note: Empty lines are kept empty. /// </summary> - /// <param name="instance">The string to modify.</param> - /// <param name="pos">Position at which to insert the substring.</param> - /// <param name="what">Substring to insert.</param> - /// <returns> - /// The string with <paramref name="what"/> inserted at the given - /// position <paramref name="pos"/>. - /// </returns> - public static string Insert(this string instance, int pos, string what) + /// <param name="instance">The string to add indentation to.</param> + /// <param name="prefix">The string to use as indentation.</param> + /// <returns>The string with indentation added.</returns> + public static string Indent(this string instance, string prefix) { - return instance.Insert(pos, what); + var sb = new StringBuilder(); + int lineStart = 0; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (i == lineStart) + { + sb.Append(c); // Leave empty lines empty. + } + else + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart, i - lineStart + 1)); + } + lineStart = i + 1; + } + } + if (lineStart != instance.Length) + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart)); + } + return sb.ToString(); } /// <summary> @@ -873,19 +952,94 @@ namespace Godot return instance.IsSubsequenceOf(text, caseSensitive: false); } + private static readonly char[] _invalidFileNameCharacters = { ':', '/', '\\', '?', '*', '"', '|', '%', '<', '>' }; + + /// <summary> + /// Returns <see langword="true"/> if this string is free from characters that + /// aren't allowed in file names. + /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid file name.</returns> + public static bool IsValidFileName(this string instance) + { + var stripped = instance.Trim(); + if (instance != stripped) + return false; + + if (string.IsNullOrEmpty(stripped)) + return false; + + return instance.IndexOfAny(_invalidFileNameCharacters) == -1; + } + /// <summary> - /// Check whether the string contains a valid <see langword="float"/>. + /// Returns <see langword="true"/> if this string contains a valid <see langword="float"/>. + /// This is inclusive of integers, and also supports exponents. /// </summary> + /// <example> + /// <code> + /// GD.Print("1.7".IsValidFloat()) // Prints "True" + /// GD.Print("24".IsValidFloat()) // Prints "True" + /// GD.Print("7e3".IsValidFloat()) // Prints "True" + /// GD.Print("Hello".IsValidFloat()) // Prints "False" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid floating point number.</returns> public static bool IsValidFloat(this string instance) { - float f; - return float.TryParse(instance, out f); + return float.TryParse(instance, out _); + } + + /// <summary> + /// Returns <see langword="true"/> if this string contains a valid hexadecimal number. + /// If <paramref name="withPrefix"/> is <see langword="true"/>, then a validity of the + /// hexadecimal number is determined by <c>0x</c> prefix, for instance: <c>0xDEADC0DE</c>. + /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="withPrefix">If the string must contain the <c>0x</c> prefix to be valid.</param> + /// <returns>If the string contains a valid hexadecimal number.</returns> + public static bool IsValidHexNumber(this string instance, bool withPrefix = false) + { + if (string.IsNullOrEmpty(instance)) + return false; + + int from = 0; + if (instance.Length != 1 && instance[0] == '+' || instance[0] == '-') + { + from++; + } + + if (withPrefix) + { + if (instance.Length < 3) + return false; + if (instance[from] != '0' || instance[from + 1] != 'x') + return false; + from += 2; + } + + for (int i = from; i < instance.Length; i++) + { + char c = instance[i]; + if (IsHexDigit(c)) + continue; + + return false; + } + + return true; + + static bool IsHexDigit(char c) + { + return char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } } /// <summary> - /// Check whether the string contains a valid color in HTML notation. + /// Returns <see langword="true"/> if this string contains a valid color in hexadecimal + /// HTML notation. Other HTML notations such as named colors or <c>hsl()</c> aren't + /// considered valid by this method and will return <see langword="false"/>. /// </summary> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid HTML color.</returns> @@ -895,10 +1049,17 @@ namespace Godot } /// <summary> - /// Check whether the string is a valid identifier. As is common in - /// programming languages, a valid identifier may contain only letters, - /// digits and underscores (_) and the first character may not be a digit. + /// Returns <see langword="true"/> if this string is a valid identifier. + /// A valid identifier may contain only letters, digits and underscores (<c>_</c>) + /// and the first character may not be a digit. /// </summary> + /// <example> + /// <code> + /// GD.Print("good_ident_1".IsValidIdentifier()) // Prints "True" + /// GD.Print("1st_bad_ident".IsValidIdentifier()) // Prints "False" + /// GD.Print("bad_ident_#2".IsValidIdentifier()) // Prints "False" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid identifier.</returns> public static bool IsValidIdentifier(this string instance) @@ -926,38 +1087,73 @@ namespace Godot } /// <summary> - /// Check whether the string contains a valid integer. + /// Returns <see langword="true"/> if this string contains a valid <see langword="int"/>. /// </summary> + /// <example> + /// <code> + /// GD.Print("7".IsValidInt()) // Prints "True" + /// GD.Print("14.6".IsValidInt()) // Prints "False" + /// GD.Print("L".IsValidInt()) // Prints "False" + /// GD.Print("+3".IsValidInt()) // Prints "True" + /// GD.Print("-12".IsValidInt()) // Prints "True" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid integer.</returns> - public static bool IsValidInteger(this string instance) + public static bool IsValidInt(this string instance) { - int f; - return int.TryParse(instance, out f); + return int.TryParse(instance, out _); } /// <summary> - /// Check whether the string contains a valid IP address. + /// Returns <see langword="true"/> if this string contains only a well-formatted + /// IPv4 or IPv6 address. This method considers reserved IP addresses such as + /// <c>0.0.0.0</c> as valid. /// </summary> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid IP address.</returns> public static bool IsValidIPAddress(this string instance) { - // TODO: Support IPv6 addresses - string[] ip = instance.Split("."); + if (instance.Contains(':')) + { + string[] ip = instance.Split(':'); - if (ip.Length != 4) - return false; + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (n.Length == 0) + continue; + + if (n.IsValidHexNumber(withPrefix: false)) + { + long nint = n.HexToInt(); + if (nint < 0 || nint > 0xffff) + return false; - for (int i = 0; i < ip.Length; i++) + continue; + } + + if (!n.IsValidIPAddress()) + return false; + } + } + else { - string n = ip[i]; - if (!n.IsValidInteger()) - return false; + string[] ip = instance.Split('.'); - int val = n.ToInt(); - if (val < 0 || val > 255) + if (ip.Length != 4) return false; + + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (!n.IsValidInt()) + return false; + + int val = n.ToInt(); + if (val < 0 || val > 255) + return false; + } } return true; @@ -1003,41 +1199,20 @@ namespace Godot } /// <summary> - /// Returns the length of the string in characters. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <returns>The length of the string.</returns> - public static int Length(this string instance) - { - return instance.Length; - } - - /// <summary> /// Returns a copy of the string with characters removed from the left. + /// The <paramref name="chars"/> argument is a string specifying the set of characters + /// to be removed. + /// Note: The <paramref name="chars"/> is not a prefix. See <see cref="TrimPrefix"/> + /// method that will remove a single prefix string rather than a set of characters. /// </summary> /// <seealso cref="RStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the left.</returns> + [Obsolete("Use string.TrimStart instead.")] public static string LStrip(this string instance, string chars) { - int len = instance.Length; - int beg; - - for (beg = 0; beg < len; beg++) - { - if (chars.Find(instance[beg]) == -1) - { - break; - } - } - - if (beg == 0) - { - return instance; - } - - return instance.Substr(beg, len - beg); + return instance.TrimStart(chars.ToCharArray()); } /// <summary> @@ -1117,10 +1292,9 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static byte[] MD5Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_buffer(instanceStr, out var md5Buffer); - using (md5Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(md5Buffer); +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + return MD5.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5351 } /// <summary> @@ -1131,10 +1305,7 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static string MD5Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_text(instanceStr, out var md5Text); - using (md5Text) - return Marshaling.ConvertStringToManaged(md5Text); + return instance.MD5Buffer().HexEncode(); } /// <summary> @@ -1151,17 +1322,6 @@ namespace Godot } /// <summary> - /// Returns the character code at position <paramref name="at"/>. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <param name="at">The position int the string for the character to check.</param> - /// <returns>The character code.</returns> - public static int OrdAt(this string instance, int at) - { - return instance[at]; - } - - /// <summary> /// Format a number to have an exact number of <paramref name="digits"/> /// after the decimal point. /// </summary> @@ -1282,34 +1442,47 @@ namespace Godot } /// <summary> - /// Perform a search for a substring, but start from the end of the string instead of the beginning. + /// Returns the index of the last occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// </summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to search in the string.</param> /// <param name="from">The position at which to start searching.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The position at which the substring was found, or -1 if not found.</returns> - public static int RFind(this string instance, string what, int from = -1) + public static int RFind(this string instance, string what, int from = -1, bool caseSensitive = true) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfind(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary> - /// Perform a search for a substring, but start from the end of the string instead of the beginning. - /// Also search case-insensitive. + /// Returns the index of the last case-insensitive occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// </summary> - /// <seealso cref="RFind(string, string, int)"/> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to search in the string.</param> /// <param name="from">The position at which to start searching.</param> /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFindN(this string instance, string what, int from = -1) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfindn(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -1332,30 +1505,43 @@ namespace Godot /// <summary> /// Returns a copy of the string with characters removed from the right. + /// The <paramref name="chars"/> argument is a string specifying the set of characters + /// to be removed. + /// Note: The <paramref name="chars"/> is not a suffix. See <see cref="TrimSuffix"/> + /// method that will remove a single suffix string rather than a set of characters. /// </summary> /// <seealso cref="LStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the right.</returns> + [Obsolete("Use string.TrimEnd instead.")] public static string RStrip(this string instance, string chars) { - int len = instance.Length; - int end; - - for (end = len - 1; end >= 0; end--) - { - if (chars.Find(instance[end]) == -1) - { - break; - } - } + return instance.TrimEnd(chars.ToCharArray()); + } - if (end == len - 1) - { - return instance; - } + /// <summary> + /// Returns the SHA-1 hash of the string as an array of bytes. + /// </summary> + /// <seealso cref="SHA1Text(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-1 hash of the string.</returns> + public static byte[] SHA1Buffer(this string instance) + { +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms + return SHA1.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5350 + } - return instance.Substr(0, end + 1); + /// <summary> + /// Returns the SHA-1 hash of the string as a string. + /// </summary> + /// <seealso cref="SHA1Buffer(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-1 hash of the string.</returns> + public static string SHA1Text(this string instance) + { + return instance.SHA1Buffer().HexEncode(); } /// <summary> @@ -1366,10 +1552,7 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static byte[] SHA256Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_buffer(instanceStr, out var sha256Buffer); - using (sha256Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(sha256Buffer); + return SHA256.HashData(Encoding.UTF8.GetBytes(instance)); } /// <summary> @@ -1380,10 +1563,7 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static string SHA256Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_text(instanceStr, out var sha256Text); - using (sha256Text) - return Marshaling.ConvertStringToManaged(sha256Text); + return instance.SHA256Buffer().HexEncode(); } /// <summary> @@ -1455,7 +1635,7 @@ namespace Godot /// <returns>The array of strings split from the string.</returns> public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { - return instance.Split(new[] { divisor }, + return instance.Split(divisor, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); } @@ -1503,8 +1683,10 @@ namespace Godot }; /// <summary> - /// Returns a copy of the string stripped of any non-printable character at the beginning and the end. - /// The optional arguments are used to toggle stripping on the left and right edges respectively. + /// Returns a copy of the string stripped of any non-printable character + /// (including tabulations, spaces and line breaks) at the beginning and the end. + /// The optional arguments are used to toggle stripping on the left and right + /// edges respectively. /// </summary> /// <param name="instance">The string to strip.</param> /// <param name="left">If the left side should be stripped.</param> @@ -1522,6 +1704,30 @@ namespace Godot return instance.TrimEnd(_nonPrintable); } + + /// <summary> + /// Returns a copy of the string stripped of any escape character. + /// These include all non-printable control characters of the first page + /// of the ASCII table (< 32), such as tabulation (<c>\t</c>) and + /// newline (<c>\n</c> and <c>\r</c>) characters, but not spaces. + /// </summary> + /// <param name="instance">The string to strip.</param> + /// <returns>The string stripped of any escape characters.</returns> + public static string StripEscapes(this string instance) + { + var sb = new StringBuilder(); + for (int i = 0; i < instance.Length; i++) + { + // Escape characters on first page of the ASCII table, before 32 (Space). + if (instance[i] < 32) + continue; + + sb.Append(instance[i]); + } + + return sb.ToString(); + } + /// <summary> /// Returns part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. /// </summary> @@ -1539,13 +1745,15 @@ namespace Godot /// <summary> /// Converts the String (which is a character array) to PackedByteArray (which is an array of bytes). - /// The conversion is speeded up in comparison to <see cref="ToUTF8(string)"/> with the assumption - /// that all the characters the String contains are only ASCII characters. + /// The conversion is faster compared to <see cref="ToUTF8Buffer(string)"/>, + /// as this method assumes that all the characters in the String are ASCII characters. /// </summary> - /// <seealso cref="ToUTF8(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> /// <param name="instance">The string to convert.</param> /// <returns>The string as ASCII encoded bytes.</returns> - public static byte[] ToAscii(this string instance) + public static byte[] ToASCIIBuffer(this string instance) { return Encoding.ASCII.GetBytes(instance); } @@ -1573,41 +1781,76 @@ namespace Godot } /// <summary> - /// Returns the string converted to lowercase. + /// Converts the string (which is an array of characters) to an UTF-16 encoded array of bytes. /// </summary> - /// <seealso cref="ToUpper(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> /// <param name="instance">The string to convert.</param> - /// <returns>The string converted to lowercase.</returns> - public static string ToLower(this string instance) + /// <returns>The string as UTF-16 encoded bytes.</returns> + public static byte[] ToUTF16Buffer(this string instance) { - return instance.ToLower(); + return Encoding.Unicode.GetBytes(instance); } /// <summary> - /// Returns the string converted to uppercase. + /// Converts the string (which is an array of characters) to an UTF-32 encoded array of bytes. /// </summary> - /// <seealso cref="ToLower(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> /// <param name="instance">The string to convert.</param> - /// <returns>The string converted to uppercase.</returns> - public static string ToUpper(this string instance) + /// <returns>The string as UTF-32 encoded bytes.</returns> + public static byte[] ToUTF32Buffer(this string instance) { - return instance.ToUpper(); + return Encoding.UTF32.GetBytes(instance); } /// <summary> - /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). - /// The conversion is a bit slower than <see cref="ToAscii(string)"/>, but supports all UTF-8 characters. - /// Therefore, you should prefer this function over <see cref="ToAscii(string)"/>. + /// Converts the string (which is an array of characters) to an UTF-8 encoded array of bytes. + /// The conversion is a bit slower than <see cref="ToASCIIBuffer(string)"/>, + /// but supports all UTF-8 characters. Therefore, you should prefer this function + /// over <see cref="ToASCIIBuffer(string)"/>. /// </summary> - /// <seealso cref="ToAscii(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> /// <param name="instance">The string to convert.</param> /// <returns>The string as UTF-8 encoded bytes.</returns> - public static byte[] ToUTF8(this string instance) + public static byte[] ToUTF8Buffer(this string instance) { return Encoding.UTF8.GetBytes(instance); } /// <summary> + /// Removes a given string from the start if it starts with it or leaves the string unchanged. + /// </summary> + /// <param name="instance">The string to remove the prefix from.</param> + /// <param name="prefix">The string to remove from the start.</param> + /// <returns>A copy of the string with the prefix string removed from the start.</returns> + public static string TrimPrefix(this string instance, string prefix) + { + if (instance.StartsWith(prefix)) + return instance.Substring(prefix.Length); + + return instance; + } + + /// <summary> + /// Removes a given string from the end if it ends with it or leaves the string unchanged. + /// </summary> + /// <param name="instance">The string to remove the suffix from.</param> + /// <param name="suffix">The string to remove from the end.</param> + /// <returns>A copy of the string with the suffix string removed from the end.</returns> + public static string TrimSuffix(this string instance, string suffix) + { + if (instance.EndsWith(suffix)) + return instance.Substring(0, instance.Length - suffix.Length); + + return instance; + } + + /// <summary> /// Decodes a string in URL encoded format. This is meant to /// decode parameters in a URL when receiving an HTTP request. /// This mostly wraps around <see cref="Uri.UnescapeDataString"/>, @@ -1634,6 +1877,25 @@ namespace Godot return Uri.EscapeDataString(instance); } + private const string _uniqueNodePrefix = "%"; + private static readonly string[] _invalidNodeNameCharacters = { ".", ":", "@", "/", "\"", _uniqueNodePrefix }; + + /// <summary> + /// Removes any characters from the string that are prohibited in + /// <see cref="Node"/> names (<c>.</c> <c>:</c> <c>@</c> <c>/</c> <c>"</c>). + /// </summary> + /// <param name="instance">The string to sanitize.</param> + /// <returns>The string sanitized as a valid node name.</returns> + public static string ValidateNodeName(this string instance) + { + string name = instance.Replace(_invalidNodeNameCharacters[0], ""); + for (int i = 1; i < _invalidNodeNameCharacters.Length; i++) + { + name = name.Replace(_invalidNodeNameCharacters[i], ""); + } + return name; + } + /// <summary> /// Returns a copy of the string with special characters escaped using the XML standard. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs index d354509dbf..237a4da364 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs @@ -121,6 +121,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); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 535391f447..c471eceded 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -240,7 +240,7 @@ namespace Godot /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by this vector - /// and the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. /// </summary> /// <param name="control1">Control point that defines the bezier curve.</param> /// <param name="control2">Control point that defines the bezier curve.</param> @@ -249,14 +249,28 @@ namespace Godot /// <returns>The interpolated vector.</returns> public readonly Vector2 BezierInterpolate(Vector2 control1, Vector2 control2, Vector2 end, real_t t) { - // Formula from Wikipedia article on Bezier curves - real_t omt = 1 - t; - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; + return new Vector2 + ( + Mathf.BezierInterpolate(x, control1.x, control2.x, end.x, t), + Mathf.BezierInterpolate(y, control1.y, control2.y, end.y, t) + ); + } - return this * omt3 + control1 * omt2 * t * 3 + control2 * omt * t2 * 3 + end * t3; + /// <summary> + /// Returns the derivative at the given <paramref name="t"/> on the Bezier curve defined by this vector + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. + /// </summary> + /// <param name="control1">Control point that defines the bezier curve.</param> + /// <param name="control2">Control point that defines the bezier curve.</param> + /// <param name="end">The destination value for the interpolation.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public readonly Vector2 BezierDerivative(Vector2 control1, Vector2 control2, Vector2 end, real_t t) + { + return new Vector2( + Mathf.BezierDerivative(x, control1.x, control2.x, end.x, t), + Mathf.BezierDerivative(y, control1.y, control2.y, end.y, t) + ); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 53bd0b0908..fefdee33a5 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -234,7 +234,7 @@ namespace Godot /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by this vector - /// and the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. /// </summary> /// <param name="control1">Control point that defines the bezier curve.</param> /// <param name="control2">Control point that defines the bezier curve.</param> @@ -243,14 +243,30 @@ namespace Godot /// <returns>The interpolated vector.</returns> public readonly Vector3 BezierInterpolate(Vector3 control1, Vector3 control2, Vector3 end, real_t t) { - // Formula from Wikipedia article on Bezier curves - real_t omt = 1 - t; - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; + return new Vector3 + ( + Mathf.BezierInterpolate(x, control1.x, control2.x, end.x, t), + Mathf.BezierInterpolate(y, control1.y, control2.y, end.y, t), + Mathf.BezierInterpolate(z, control1.z, control2.z, end.z, t) + ); + } - return this * omt3 + control1 * omt2 * t * 3 + control2 * omt * t2 * 3 + end * t3; + /// <summary> + /// Returns the derivative at the given <paramref name="t"/> on the Bezier curve defined by this vector + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. + /// </summary> + /// <param name="control1">Control point that defines the bezier curve.</param> + /// <param name="control2">Control point that defines the bezier curve.</param> + /// <param name="end">The destination value for the interpolation.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public readonly Vector3 BezierDerivative(Vector3 control1, Vector3 control2, Vector3 end, real_t t) + { + return new Vector3( + Mathf.BezierDerivative(x, control1.x, control2.x, end.x, t), + Mathf.BezierDerivative(y, control1.y, control2.y, end.y, t), + Mathf.BezierDerivative(z, control1.z, control2.z, end.y, t) + ); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index e3fb254f49..503e5abe37 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -101,9 +101,8 @@ <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 +122,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 +131,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. |