diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2023-02-04 00:24:06 +0100 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2023-02-04 00:24:06 +0100 |
commit | bbff9fd7a401b5ef39bc1f9337a0477c63dff654 (patch) | |
tree | dc0e1d2720656c48f96fc6fe9ee6a73f79f3a745 /modules/mono | |
parent | ea5cf7d4b8cf5cf988f32ef736de8a706b96f47b (diff) | |
parent | 54f8fb010c28ac0ce594a53a61ebdb8a645ce1db (diff) |
Merge pull request #71786 from raulsntos/dotnet/array
Sync C# Array with Core
Diffstat (limited to 'modules/mono')
3 files changed, 987 insertions, 36 deletions
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index a61c5403b9..8598c32760 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using Godot.NativeInterop; @@ -174,7 +175,15 @@ namespace Godot.Collections } /// <summary> - /// Duplicates this <see cref="Array"/>. + /// Returns a copy of the <see cref="Array"/>. + /// If <paramref name="deep"/> is <see langword="true"/>, a deep copy if performed: + /// all nested arrays and dictionaries are duplicated and will not be shared with + /// the original array. If <see langword="false"/>, a shallow copy is made and + /// references to the original nested arrays and dictionaries are kept, so that + /// modifying a sub-array or dictionary in the copy will also impact those + /// referenced in the source array. Note that any <see cref="GodotObject"/> derived + /// elements will be shallow copied regardless of the <paramref name="deep"/> + /// setting. /// </summary> /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> /// <returns>A new Godot Array.</returns> @@ -187,7 +196,102 @@ namespace Godot.Collections } /// <summary> - /// Resizes this <see cref="Array"/> to the given size. + /// Assigns the given value to all elements in the array. This can typically be + /// used together with <see cref="Resize(int)"/> to create an array with a given + /// size and initialized elements. + /// Note: If <paramref name="value"/> is of a reference type (<see cref="GodotObject"/> + /// derived, <see cref="Array"/> or <see cref="Dictionary"/>, etc.) then the array + /// is filled with the references to the same object, i.e. no duplicates are + /// created. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array(); + /// array.Resize(10); + /// array.Fill(0); // Initialize the 10 elements to 0. + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <param name="value">The value to fill the array with.</param> + public void Fill(Variant value) + { + ThrowIfReadOnly(); + + godot_variant variantValue = (godot_variant)value.NativeVar; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_fill(ref self, variantValue); + } + + /// <summary> + /// Returns the maximum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="null"/> + /// is returned. + /// </summary> + /// <returns>The maximum value contained in the array.</returns> + public Variant Max() + { + godot_variant resVariant; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_max(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// <summary> + /// Returns the minimum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="null"/> + /// is returned. + /// </summary> + /// <returns>The minimum value contained in the array.</returns> + public Variant Min() + { + godot_variant resVariant; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_min(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// <summary> + /// Returns a random value from the target array. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array { 1, 2, 3, 4 }; + /// GD.Print(array.PickRandom()); // Prints either of the four numbers. + /// </code> + /// </example> + /// <returns>A random element from the array.</returns> + public Variant PickRandom() + { + godot_variant resVariant; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_pick_random(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// <summary> + /// Compares this <see cref="Array"/> against the <paramref name="other"/> + /// <see cref="Array"/> recursively. Returns <see langword="true"/> if the + /// sizes and contents of the arrays are equal, <see langword="false"/> + /// otherwise. + /// </summary> + /// <param name="other">The other array to compare against.</param> + /// <returns> + /// <see langword="true"/> if the sizes and contents of the arrays are equal, + /// <see langword="false"/> otherwise. + /// </returns> + public bool RecursiveEqual(Array other) + { + var self = (godot_array)NativeValue; + var otherVariant = (godot_array)other.NativeValue; + return NativeFuncs.godotsharp_array_recursive_equal(ref self, otherVariant).ToBool(); + } + + /// <summary> + /// Resizes the array to contain a different number of elements. If the array + /// size is smaller, elements are cleared, if bigger, new elements are + /// <see langword="null"/>. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -203,7 +307,25 @@ namespace Godot.Collections } /// <summary> - /// Shuffles the contents of this <see cref="Array"/> into a random order. + /// Reverses the order of the elements in the array. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Reverse() + { + ThrowIfReadOnly(); + + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_reverse(ref self); + } + + /// <summary> + /// Shuffles the array such that the items will have a random order. + /// This method uses the global random number generator common to methods + /// such as <see cref="GD.Randi"/>. Call <see cref="GD.Randomize"/> to + /// ensure that a new seed will be used each time if you want + /// non-reproducible shuffling. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -217,7 +339,104 @@ namespace Godot.Collections } /// <summary> - /// Concatenates these two <see cref="Array"/>s. + /// Creates a shallow copy of a range of elements in the source <see cref="Array"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array Slice(int start) + { + if (start < 0 || start > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + return GetSliceRange(start, Count, step: 1, deep: false); + } + + /// <summary> + /// Creates a shallow copy of a range of elements in the source <see cref="Array"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// -or- + /// <paramref name="length"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="length">The length of the range.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + // The Slice method must have this signature to get implicit Range support. + public Array Slice(int start, int length) + { + if (start < 0 || start > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + if (length < 0 || length > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + return GetSliceRange(start, start + length, step: 1, deep: false); + } + + /// <summary> + /// Returns the slice of the <see cref="Array"/>, from <paramref name="start"/> + /// (inclusive) to <paramref name="end"/> (exclusive), as a new <see cref="Array"/>. + /// The absolute value of <paramref name="start"/> and <paramref name="end"/> + /// will be clamped to the array size. + /// If either <paramref name="start"/> or <paramref name="end"/> are negative, they + /// will be relative to the end of the array (i.e. <c>arr.GetSliceRange(0, -2)</c> + /// is a shorthand for <c>arr.GetSliceRange(0, arr.Count - 2)</c>). + /// If specified, <paramref name="step"/> is the relative index between source + /// elements. It can be negative, then <paramref name="start"/> must be higher than + /// <paramref name="end"/>. For example, <c>[0, 1, 2, 3, 4, 5].GetSliceRange(5, 1, -2)</c> + /// returns <c>[5, 3]</c>. + /// If <paramref name="deep"/> is true, each element will be copied by value + /// rather than by reference. + /// </summary> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="end">The zero-based index at which the range ends.</param> + /// <param name="step">The relative index between source elements to take.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array GetSliceRange(int start, int end, int step = 1, bool deep = false) + { + godot_array newArray; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_slice(ref self, start, end, step, deep.ToGodotBool(), out newArray); + return CreateTakingOwnershipOfDisposableValue(newArray); + } + + /// <summary> + /// Sorts the array. + /// Note: The sorting algorithm used is not stable. This means that values + /// considered equal may have their order changed when using <see cref="Sort"/>. + /// Note: Strings are sorted in alphabetical order (as opposed to natural order). + /// This may lead to unexpected behavior when sorting an array of strings ending + /// with a sequence of numbers. + /// To sort with a custom predicate use + /// <see cref="Enumerable.OrderBy{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/>. + /// </summary> + /// <example> + /// <code> + /// var strings = new Godot.Collections.Array { "string1", "string2", "string10", "string11" }; + /// strings.Sort(); + /// GD.Print(strings); // Prints [string1, string10, string11, string2] + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Sort() + { + ThrowIfReadOnly(); + + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_sort(ref self); + } + + /// <summary> + /// Concatenates two <see cref="Array"/>s together, with the <paramref name="right"/> + /// being added to the end of the <see cref="Array"/> specified in <paramref name="left"/>. + /// For example, <c>[1, 2] + [3, 4]</c> results in <c>[1, 2, 3, 4]</c>. /// </summary> /// <param name="left">The first array.</param> /// <param name="right">The second array.</param> @@ -253,6 +472,9 @@ namespace Godot.Collections /// <exception cref="InvalidOperationException"> /// The property is assigned and the array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value> public unsafe Variant this[int index] { @@ -294,14 +516,146 @@ namespace Godot.Collections } /// <summary> - /// Checks if this <see cref="Array"/> contains the given item. + /// Adds the elements of the specified collection to the end of this <see cref="Array"/>. /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="collection"/> is <see langword="null"/>. + /// </exception> + /// <param name="collection">Collection of <see cref="Variant"/> items to add.</param> + public void AddRange<[MustBeVariant] T>(IEnumerable<T> collection) + { + ThrowIfReadOnly(); + + if (collection == null) + throw new ArgumentNullException(nameof(collection), "Value cannot be null."); + + // If the collection is another Godot Array, we can add the items + // with a single interop call. + if (collection is Array array) + { + var self = (godot_array)NativeValue; + var collectionNative = (godot_array)array.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + if (collection is Array<T> typedArray) + { + var self = (godot_array)NativeValue; + var collectionNative = (godot_array)typedArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + + // If we can retrieve the count of the collection without enumerating it + // (e.g.: the collections is a List<T>), use it to resize the array once + // instead of growing it as we add items. + if (collection.TryGetNonEnumeratedCount(out int count)) + { + Resize(Count + count); + + using var enumerator = collection.GetEnumerator(); + + for (int i = 0; i < count; i++) + { + enumerator.MoveNext(); + this[count + i] = Variant.From(enumerator.Current); + } + + return; + } + + foreach (var item in collection) + { + Add(Variant.From(item)); + } + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(int, int, Variant)"/> on an + /// unsorted array results in unexpected behavior. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0. + /// -or- + /// <paramref name="count"/> is less than 0. + /// </exception> + /// <exception cref="ArgumentException"> + /// <paramref name="index"/> and <paramref name="count"/> do not denote + /// a valid range in the <see cref="Array"/>. + /// </exception> + /// <param name="index">The starting index of the range to search.</param> + /// <param name="count">The length of the range to search.</param> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(int index, int count, Variant item) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), "index cannot be negative."); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (Count - index < count) + throw new ArgumentException("length is out of bounds or count is greater than the number of elements."); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_binary_search(ref self, index, count, variantValue); + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(Variant)"/> on an unsorted + /// array results in unexpected behavior. + /// </summary> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(Variant item) + { + return BinarySearch(0, Count, item); + } + + /// <summary> + /// Returns <see langword="true"/> if the array contains the given value. + /// </summary> + /// <example> + /// <code> + /// var arr = new Godot.Collections.Array { "inside", 7 }; + /// GD.Print(arr.Contains("inside")); // True + /// GD.Print(arr.Contains("outside")); // False + /// GD.Print(arr.Contains(7)); // True + /// GD.Print(arr.Contains("7")); // False + /// </code> + /// </example> /// <param name="item">The <see cref="Variant"/> item to look for.</param> /// <returns>Whether or not this array contains the given item.</returns> public bool Contains(Variant item) => IndexOf(item) != -1; /// <summary> - /// Erases all items from this <see cref="Array"/>. + /// Clears the array. This is the equivalent to using <see cref="Resize(int)"/> + /// with a size of <c>0</c> /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -309,27 +663,104 @@ namespace Godot.Collections public void Clear() => Resize(0); /// <summary> - /// Searches this <see cref="Array"/> for an item - /// and returns its index or -1 if not found. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. /// </summary> /// <param name="item">The <see cref="Variant"/> item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> public int IndexOf(Variant item) { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + godot_variant variantValue = (godot_variant)item.NativeVar; var self = (godot_array)NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } /// <summary> - /// Inserts a new item at a given position in the array. - /// The position must be a valid position of an existing item, - /// or the position at the end of the array. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int IndexOf(Variant item, int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, Count - 1); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item, int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Inserts a new element at a given position in the array. The position + /// must be valid, or at the end of the array (<c>pos == Count - 1</c>). /// Existing items will be moved to the right. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index to insert at.</param> /// <param name="item">The <see cref="Variant"/> item to insert.</param> public void Insert(int index, Variant item) @@ -367,11 +798,16 @@ namespace Godot.Collections } /// <summary> - /// Removes an element from this <see cref="Array"/> by index. + /// Removes an element from the array by index. + /// To remove an element by searching for its value, use + /// <see cref="Remove(Variant)"/> instead. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { @@ -424,6 +860,9 @@ namespace Godot.Collections /// Copies the elements of this <see cref="Array"/> to the given /// <see cref="Variant"/> C# array, starting at the given index. /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="array">The array to copy to.</param> /// <param name="arrayIndex">The index to start at.</param> public void CopyTo(Variant[] array, int arrayIndex) @@ -518,6 +957,9 @@ namespace Godot.Collections /// <summary> /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed. /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> internal void GetVariantBorrowElementAt(int index, out godot_variant elem) { if (index < 0 || index >= Count) @@ -658,6 +1100,97 @@ namespace Godot.Collections } /// <summary> + /// Assigns the given value to all elements in the array. This can typically be + /// used together with <see cref="Resize(int)"/> to create an array with a given + /// size and initialized elements. + /// Note: If <paramref name="value"/> is of a reference type (<see cref="GodotObject"/> + /// derived, <see cref="Array"/> or <see cref="Dictionary"/>, etc.) then the array + /// is filled with the references to the same object, i.e. no duplicates are + /// created. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array<int>(); + /// array.Resize(10); + /// array.Fill(0); // Initialize the 10 elements to 0. + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <param name="value">The value to fill the array with.</param> + public void Fill(T value) + { + ThrowIfReadOnly(); + + godot_variant variantValue = VariantUtils.CreateFrom(value); + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_fill(ref self, variantValue); + } + + /// <summary> + /// Returns the maximum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="default"/> + /// is returned. + /// </summary> + /// <returns>The maximum value contained in the array.</returns> + public T Max() + { + godot_variant resVariant; + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_max(ref self, out resVariant); + return VariantUtils.ConvertTo<T>(resVariant); + } + + /// <summary> + /// Returns the minimum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="default"/> + /// is returned. + /// </summary> + /// <returns>The minimum value contained in the array.</returns> + public T Min() + { + godot_variant resVariant; + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_min(ref self, out resVariant); + return VariantUtils.ConvertTo<T>(resVariant); + } + + /// <summary> + /// Returns a random value from the target array. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array<int> { 1, 2, 3, 4 }; + /// GD.Print(array.PickRandom()); // Prints either of the four numbers. + /// </code> + /// </example> + /// <returns>A random element from the array.</returns> + public T PickRandom() + { + godot_variant resVariant; + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_pick_random(ref self, out resVariant); + return VariantUtils.ConvertTo<T>(resVariant); + } + + /// <summary> + /// Compares this <see cref="Array{T}"/> against the <paramref name="other"/> + /// <see cref="Array{T}"/> recursively. Returns <see langword="true"/> if the + /// sizes and contents of the arrays are equal, <see langword="false"/> + /// otherwise. + /// </summary> + /// <param name="other">The other array to compare against.</param> + /// <returns> + /// <see langword="true"/> if the sizes and contents of the arrays are equal, + /// <see langword="false"/> otherwise. + /// </returns> + public bool RecursiveEqual(Array<T> other) + { + return _underlyingArray.RecursiveEqual(other._underlyingArray); + } + + /// <summary> /// Resizes this <see cref="Array{T}"/> to the given size. /// </summary> /// <exception cref="InvalidOperationException"> @@ -671,7 +1204,22 @@ namespace Godot.Collections } /// <summary> - /// Shuffles the contents of this <see cref="Array{T}"/> into a random order. + /// Reverses the order of the elements in the array. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Reverse() + { + _underlyingArray.Reverse(); + } + + /// <summary> + /// Shuffles the array such that the items will have a random order. + /// This method uses the global random number generator common to methods + /// such as <see cref="GD.Randi"/>. Call <see cref="GD.Randomize"/> to + /// ensure that a new seed will be used each time if you want + /// non-reproducible shuffling. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -682,7 +1230,89 @@ namespace Godot.Collections } /// <summary> - /// Concatenates these two <see cref="Array{T}"/>s. + /// Creates a shallow copy of a range of elements in the source <see cref="Array{T}"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array<T> Slice(int start) + { + return GetSliceRange(start, Count, step: 1, deep: false); + } + + /// <summary> + /// Creates a shallow copy of a range of elements in the source <see cref="Array{T}"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// -or- + /// <paramref name="length"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="length">The length of the range.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + // The Slice method must have this signature to get implicit Range support. + public Array<T> Slice(int start, int length) + { + return GetSliceRange(start, start + length, step: 1, deep: false); + } + + /// <summary> + /// Returns the slice of the <see cref="Array{T}"/>, from <paramref name="start"/> + /// (inclusive) to <paramref name="end"/> (exclusive), as a new <see cref="Array{T}"/>. + /// The absolute value of <paramref name="start"/> and <paramref name="end"/> + /// will be clamped to the array size. + /// If either <paramref name="start"/> or <paramref name="end"/> are negative, they + /// will be relative to the end of the array (i.e. <c>arr.GetSliceRange(0, -2)</c> + /// is a shorthand for <c>arr.GetSliceRange(0, arr.Count - 2)</c>). + /// If specified, <paramref name="step"/> is the relative index between source + /// elements. It can be negative, then <paramref name="start"/> must be higher than + /// <paramref name="end"/>. For example, <c>[0, 1, 2, 3, 4, 5].GetSliceRange(5, 1, -2)</c> + /// returns <c>[5, 3]</c>. + /// If <paramref name="deep"/> is true, each element will be copied by value + /// rather than by reference. + /// </summary> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="end">The zero-based index at which the range ends.</param> + /// <param name="step">The relative index between source elements to take.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array<T> GetSliceRange(int start, int end, int step = 1, bool deep = false) + { + return new Array<T>(_underlyingArray.GetSliceRange(start, end, step, deep)); + } + + /// <summary> + /// Sorts the array. + /// Note: The sorting algorithm used is not stable. This means that values + /// considered equal may have their order changed when using <see cref="Sort"/>. + /// Note: Strings are sorted in alphabetical order (as opposed to natural order). + /// This may lead to unexpected behavior when sorting an array of strings ending + /// with a sequence of numbers. + /// To sort with a custom predicate use + /// <see cref="Enumerable.OrderBy{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/>. + /// </summary> + /// <example> + /// <code> + /// var strings = new Godot.Collections.Array<string> { "string1", "string2", "string10", "string11" }; + /// strings.Sort(); + /// GD.Print(strings); // Prints [string1, string10, string11, string2] + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Sort() + { + _underlyingArray.Sort(); + } + + /// <summary> + /// Concatenates two <see cref="Array{T}"/>s together, with the <paramref name="right"/> + /// being added to the end of the <see cref="Array{T}"/> specified in <paramref name="left"/>. + /// For example, <c>[1, 2] + [3, 4]</c> results in <c>[1, 2, 3, 4]</c>. /// </summary> /// <param name="left">The first array.</param> /// <param name="right">The second array.</param> @@ -706,12 +1336,15 @@ namespace Godot.Collections // IList<T> /// <summary> - /// Returns the value at the given <paramref name="index"/>. + /// Returns the item at the given <paramref name="index"/>. /// </summary> /// <exception cref="InvalidOperationException"> /// The property is assigned and the array is read-only. /// </exception> - /// <value>The value at the given <paramref name="index"/>.</value> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value> public unsafe T this[int index] { get @@ -735,29 +1368,106 @@ namespace Godot.Collections } /// <summary> - /// Searches this <see cref="Array{T}"/> for an item - /// and returns its index or -1 if not found. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. /// </summary> - /// <param name="item">The item to search for.</param> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> public int IndexOf(T item) { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } /// <summary> - /// Inserts a new item at a given position in the <see cref="Array{T}"/>. - /// The position must be a valid position of an existing item, - /// or the position at the end of the array. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int IndexOf(T item, int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, Count - 1); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item, int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Inserts a new element at a given position in the array. The position + /// must be valid, or at the end of the array (<c>pos == Count - 1</c>). /// Existing items will be moved to the right. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index to insert at.</param> - /// <param name="item">The item to insert.</param> + /// <param name="item">The <see cref="Variant"/> item to insert.</param> public void Insert(int index, T item) { ThrowIfReadOnly(); @@ -771,11 +1481,16 @@ namespace Godot.Collections } /// <summary> - /// Removes an element from this <see cref="Array{T}"/> by index. + /// Removes an element from the array by index. + /// To remove an element by searching for its value, use + /// <see cref="Remove(T)"/> instead. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { @@ -814,8 +1529,7 @@ namespace Godot.Collections /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> - /// <param name="item">The item to add.</param> - /// <returns>The new size after adding the item.</returns> + /// <param name="item">The <see cref="Variant"/> item to add.</param> public void Add(T item) { ThrowIfReadOnly(); @@ -826,7 +1540,130 @@ namespace Godot.Collections } /// <summary> - /// Erases all items from this <see cref="Array{T}"/>. + /// Adds the elements of the specified collection to the end of this <see cref="Array{T}"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="collection"/> is <see langword="null"/>. + /// </exception> + /// <param name="collection">Collection of <see cref="Variant"/> items to add.</param> + public void AddRange(IEnumerable<T> collection) + { + ThrowIfReadOnly(); + + if (collection == null) + throw new ArgumentNullException(nameof(collection), "Value cannot be null."); + + // If the collection is another Godot Array, we can add the items + // with a single interop call. + if (collection is Array array) + { + var self = (godot_array)_underlyingArray.NativeValue; + var collectionNative = (godot_array)array.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + if (collection is Array<T> typedArray) + { + var self = (godot_array)_underlyingArray.NativeValue; + var collectionNative = (godot_array)typedArray._underlyingArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + + // If we can retrieve the count of the collection without enumerating it + // (e.g.: the collections is a List<T>), use it to resize the array once + // instead of growing it as we add items. + if (collection.TryGetNonEnumeratedCount(out int count)) + { + Resize(Count + count); + + using var enumerator = collection.GetEnumerator(); + + for (int i = 0; i < count; i++) + { + enumerator.MoveNext(); + this[count + i] = enumerator.Current; + } + + return; + } + + foreach (var item in collection) + { + Add(item); + } + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(int, int, T)"/> on an unsorted + /// array results in unexpected behavior. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0. + /// -or- + /// <paramref name="count"/> is less than 0. + /// </exception> + /// <exception cref="ArgumentException"> + /// <paramref name="index"/> and <paramref name="count"/> do not denote + /// a valid range in the <see cref="Array{T}"/>. + /// </exception> + /// <param name="index">The starting index of the range to search.</param> + /// <param name="count">The length of the range to search.</param> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(int index, int count, T item) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), "index cannot be negative."); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (Count - index < count) + throw new ArgumentException("length is out of bounds or count is greater than the number of elements."); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + using var variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_binary_search(ref self, index, count, variantValue); + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(T)"/> on an unsorted + /// array results in unexpected behavior. + /// </summary> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(T item) + { + return BinarySearch(0, Count, item); + } + + /// <summary> + /// Clears the array. This is the equivalent to using <see cref="Resize(int)"/> + /// with a size of <c>0</c> /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -837,8 +1674,17 @@ namespace Godot.Collections } /// <summary> - /// Checks if this <see cref="Array{T}"/> contains the given item. + /// Returns <see langword="true"/> if the array contains the given value. /// </summary> + /// <example> + /// <code> + /// var arr = new Godot.Collections.Array<string> { "inside", "7" }; + /// GD.Print(arr.Contains("inside")); // True + /// GD.Print(arr.Contains("outside")); // False + /// GD.Print(arr.Contains(7)); // False + /// GD.Print(arr.Contains("7")); // True + /// </code> + /// </example> /// <param name="item">The item to look for.</param> /// <returns>Whether or not this array contains the given item.</returns> public bool Contains(T item) => IndexOf(item) != -1; @@ -847,6 +1693,9 @@ namespace Godot.Collections /// Copies the elements of this <see cref="Array{T}"/> to the given /// C# array, starting at the given index. /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="array">The C# array to copy to.</param> /// <param name="arrayIndex">The index to start at.</param> public void CopyTo(T[] array, int arrayIndex) @@ -876,7 +1725,7 @@ namespace Godot.Collections } /// <summary> - /// Removes the first occurrence of the specified value + /// Removes the first occurrence of the specified <paramref name="item"/> /// from this <see cref="Array{T}"/>. /// </summary> /// <exception cref="InvalidOperationException"> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 8ac18c4314..3d72ee0036 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -365,21 +365,44 @@ namespace Godot.NativeInterop public static partial int godotsharp_array_add(ref godot_array p_self, in godot_variant p_item); + public static partial int godotsharp_array_add_range(ref godot_array p_self, in godot_array p_collection); + + public static partial int godotsharp_array_binary_search(ref godot_array p_self, int p_index, int p_count, in godot_variant p_value); + public static partial void godotsharp_array_duplicate(ref godot_array p_self, godot_bool p_deep, out godot_array r_dest); - public static partial int godotsharp_array_index_of(ref godot_array p_self, in godot_variant p_item); + public static partial void godotsharp_array_fill(ref godot_array p_self, in godot_variant p_value); + + public static partial int godotsharp_array_index_of(ref godot_array p_self, in godot_variant p_item, int p_index = 0); public static partial void godotsharp_array_insert(ref godot_array p_self, int p_index, in godot_variant p_item); + public static partial int godotsharp_array_last_index_of(ref godot_array p_self, in godot_variant p_item, int p_index); + + public static partial void godotsharp_array_make_read_only(ref godot_array p_self); + + public static partial void godotsharp_array_max(ref godot_array p_self, out godot_variant r_value); + + public static partial void godotsharp_array_min(ref godot_array p_self, out godot_variant r_value); + + public static partial void godotsharp_array_pick_random(ref godot_array p_self, out godot_variant r_value); + + public static partial godot_bool godotsharp_array_recursive_equal(ref godot_array p_self, in godot_array p_other); + public static partial void godotsharp_array_remove_at(ref godot_array p_self, int p_index); public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size); - public static partial void godotsharp_array_make_read_only(ref godot_array p_self); + public static partial void godotsharp_array_reverse(ref godot_array p_self); public static partial void godotsharp_array_shuffle(ref godot_array p_self); + public static partial void godotsharp_array_slice(ref godot_array p_self, int p_start, int p_end, + int p_step, godot_bool p_deep, out godot_array r_dest); + + public static partial void godotsharp_array_sort(ref godot_array p_self); + public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str); // Dictionary diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index a90b07620c..306ac333eb 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -992,18 +992,78 @@ int32_t godotsharp_array_add(Array *p_self, const Variant *p_item) { return p_self->size(); } +int32_t godotsharp_array_add_range(Array *p_self, const Array *p_collection) { + p_self->append_array(*p_collection); + return p_self->size(); +} + +int32_t godotsharp_array_binary_search(const Array *p_self, int32_t p_index, int32_t p_length, const Variant *p_value) { + ERR_FAIL_COND_V(p_index < 0, -1); + ERR_FAIL_COND_V(p_length < 0, -1); + ERR_FAIL_COND_V(p_self->size() - p_index < p_length, -1); + + const Variant &value = *p_value; + const Array &array = *p_self; + + int lo = p_index; + int hi = p_index + p_length - 1; + while (lo <= hi) { + int mid = lo + ((hi - lo) >> 1); + const Variant &mid_item = array[mid]; + + if (mid_item == value) { + return mid; + } + if (mid_item < value) { + lo = mid + 1; + } else { + hi = mid - 1; + } + } + + return ~lo; +} + void godotsharp_array_duplicate(const Array *p_self, bool p_deep, Array *r_dest) { memnew_placement(r_dest, Array(p_self->duplicate(p_deep))); } -int32_t godotsharp_array_index_of(const Array *p_self, const Variant *p_item) { - return p_self->find(*p_item); +void godotsharp_array_fill(Array *p_self, const Variant *p_value) { + p_self->fill(*p_value); +} + +int32_t godotsharp_array_index_of(const Array *p_self, const Variant *p_item, int32_t p_index = 0) { + return p_self->find(*p_item, p_index); } void godotsharp_array_insert(Array *p_self, int32_t p_index, const Variant *p_item) { p_self->insert(p_index, *p_item); } +int32_t godotsharp_array_last_index_of(const Array *p_self, const Variant *p_item, int32_t p_index) { + return p_self->rfind(*p_item, p_index); +} + +void godotsharp_array_make_read_only(Array *p_self) { + p_self->make_read_only(); +} + +void godotsharp_array_max(const Array *p_self, Variant *r_value) { + *r_value = p_self->max(); +} + +void godotsharp_array_min(const Array *p_self, Variant *r_value) { + *r_value = p_self->min(); +} + +void godotsharp_array_pick_random(const Array *p_self, Variant *r_value) { + *r_value = p_self->pick_random(); +} + +bool godotsharp_array_recursive_equal(const Array *p_self, const Array *p_other) { + return p_self->recursive_equal(*p_other, 0); +} + void godotsharp_array_remove_at(Array *p_self, int32_t p_index) { p_self->remove_at(p_index); } @@ -1012,14 +1072,22 @@ int32_t godotsharp_array_resize(Array *p_self, int32_t p_new_size) { return (int32_t)p_self->resize(p_new_size); } -void godotsharp_array_make_read_only(Array *p_self) { - p_self->make_read_only(); +void godotsharp_array_reverse(Array *p_self) { + p_self->reverse(); } void godotsharp_array_shuffle(Array *p_self) { p_self->shuffle(); } +void godotsharp_array_slice(Array *p_self, int32_t p_start, int32_t p_end, int32_t p_step, bool p_deep, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->slice(p_start, p_end, p_step, p_deep))); +} + +void godotsharp_array_sort(Array *p_self) { + p_self->sort(); +} + void godotsharp_array_to_string(const Array *p_self, String *r_str) { *r_str = Variant(*p_self).operator String(); } @@ -1450,13 +1518,24 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_array_destroy, (void *)godotsharp_dictionary_destroy, (void *)godotsharp_array_add, + (void *)godotsharp_array_add_range, + (void *)godotsharp_array_binary_search, (void *)godotsharp_array_duplicate, + (void *)godotsharp_array_fill, (void *)godotsharp_array_index_of, (void *)godotsharp_array_insert, + (void *)godotsharp_array_last_index_of, + (void *)godotsharp_array_make_read_only, + (void *)godotsharp_array_max, + (void *)godotsharp_array_min, + (void *)godotsharp_array_pick_random, + (void *)godotsharp_array_recursive_equal, (void *)godotsharp_array_remove_at, (void *)godotsharp_array_resize, - (void *)godotsharp_array_make_read_only, + (void *)godotsharp_array_reverse, (void *)godotsharp_array_shuffle, + (void *)godotsharp_array_slice, + (void *)godotsharp_array_sort, (void *)godotsharp_array_to_string, (void *)godotsharp_dictionary_try_get_value, (void *)godotsharp_dictionary_set_value, |