using System; using System.Collections.Generic; using System.Collections; using System.Diagnostics.CodeAnalysis; using Godot.NativeInterop; namespace Godot.Collections { /// /// Wrapper around Godot's Array class, an array of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. Otherwise prefer .NET collections /// such as or . /// public sealed class Array : IList, IDisposable { public godot_array NativeValue; /// /// Constructs a new empty . /// public Array() { NativeValue = NativeFuncs.godotsharp_array_new(); } /// /// Constructs a new from the given collection's elements. /// /// The collection of elements to construct from. /// A new Godot Array. public Array(IEnumerable collection) : this() { if (collection == null) throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); foreach (object element in collection) Add(element); } // TODO: This must be removed. Lots of silent mistakes as it takes pretty much anything. /// /// Constructs a new from the given objects. /// /// The objects to put in the new array. /// A new Godot Array. public Array(params object[] array) : this() { if (array == null) throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); NativeValue = NativeFuncs.godotsharp_array_new(); int length = array.Length; Resize(length); for (int i = 0; i < length; i++) this[i] = array[i]; } private Array(godot_array nativeValueToOwn) { NativeValue = nativeValueToOwn; } // Explicit name to make it very clear internal static Array CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn) => new Array(nativeValueToOwn); ~Array() { Dispose(false); } /// /// Disposes of this . /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void Dispose(bool disposing) { // Always dispose `NativeValue` even if disposing is true NativeValue.Dispose(); } /// /// Duplicates this . /// /// If true, performs a deep copy. /// A new Godot Array. public Array Duplicate(bool deep = false) { godot_array newArray; NativeFuncs.godotsharp_array_duplicate(ref NativeValue, deep.ToGodotBool(), out newArray); return CreateTakingOwnershipOfDisposableValue(newArray); } /// /// Resizes this to the given size. /// /// The new size of the array. /// if successful, or an error code. public Error Resize(int newSize) => NativeFuncs.godotsharp_array_resize(ref NativeValue, newSize); /// /// Shuffles the contents of this into a random order. /// public void Shuffle() => NativeFuncs.godotsharp_array_shuffle(ref NativeValue); /// /// Concatenates these two s. /// /// The first array. /// The second array. /// A new Godot Array with the contents of both arrays. public static Array operator +(Array left, Array right) { int leftCount = left.Count; int rightCount = right.Count; Array newArray = left.Duplicate(deep: false); newArray.Resize(leftCount + rightCount); for (int i = 0; i < rightCount; i++) newArray[i + leftCount] = right[i]; return newArray; } // IList bool IList.IsReadOnly => false; bool IList.IsFixedSize => false; /// /// Returns the object at the given index. /// /// The object at the given index. public unsafe object this[int index] { get { GetVariantBorrowElementAt(index, out godot_variant borrowElem); return Marshaling.variant_to_mono_object(&borrowElem); } set { if (index < 0 || index >= Count) throw new IndexOutOfRangeException(); godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref NativeValue); ptrw[index] = Marshaling.mono_object_to_variant(value); } } /// /// Adds an object to the end of this . /// This is the same as `append` or `push_back` in GDScript. /// /// The object to add. /// The new size after adding the object. public unsafe int Add(object value) { using godot_variant variantValue = Marshaling.mono_object_to_variant(value); return NativeFuncs.godotsharp_array_add(ref NativeValue, &variantValue); } /// /// Checks if this contains the given object. /// /// The item to look for. /// Whether or not this array contains the given object. public bool Contains(object value) => IndexOf(value) != -1; /// /// Erases all items from this . /// public void Clear() => Resize(0); /// /// Searches this for an object /// and returns its index or -1 if not found. /// /// The object to search for. /// The index of the object, or -1 if not found. public unsafe int IndexOf(object value) { using godot_variant variantValue = Marshaling.mono_object_to_variant(value); return NativeFuncs.godotsharp_array_index_of(ref NativeValue, &variantValue); } /// /// Inserts a new object 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. /// Existing items will be moved to the right. /// /// The index to insert at. /// The object to insert. public unsafe void Insert(int index, object value) { if (index < 0 || index > Count) throw new IndexOutOfRangeException(); using godot_variant variantValue = Marshaling.mono_object_to_variant(value); NativeFuncs.godotsharp_array_insert(ref NativeValue, index, &variantValue); } /// /// Removes the first occurrence of the specified value /// from this . /// /// The value to remove. public void Remove(object value) { int index = IndexOf(value); if (index >= 0) RemoveAt(index); } /// /// Removes an element from this by index. /// /// The index of the element to remove. public void RemoveAt(int index) { if (index < 0 || index > Count) throw new IndexOutOfRangeException(); NativeFuncs.godotsharp_array_remove_at(ref NativeValue, index); } // ICollection /// /// Returns the number of elements in this . /// This is also known as the size or length of the array. /// /// The number of elements. public int Count => NativeValue.Size; object ICollection.SyncRoot => this; bool ICollection.IsSynchronized => false; /// /// Copies the elements of this to the given /// untyped C# array, starting at the given index. /// /// The array to copy to. /// The index to start at. public void CopyTo(System.Array array, int destIndex) { if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (destIndex < 0) { throw new ArgumentOutOfRangeException(nameof(destIndex), "Number was less than the array's lower bound in the first dimension."); } int count = Count; if (array.Length < (destIndex + count)) { throw new ArgumentException( "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); } unsafe { for (int i = 0; i < count; i++) { object obj = Marshaling.variant_to_mono_object(&(*NativeValue._p)._arrayVector._ptr[i]); array.SetValue(obj, destIndex); destIndex++; } } } // IEnumerable /// /// Gets an enumerator for this . /// /// An enumerator. public IEnumerator GetEnumerator() { int count = Count; for (int i = 0; i < count; i++) { yield return this[i]; } } /// /// Converts this to a string. /// /// A string representation of this array. public override unsafe string ToString() { using godot_string str = default; NativeFuncs.godotsharp_array_to_string(ref NativeValue, &str); return Marshaling.mono_string_from_godot(str); } /// /// The variant returned via the parameter is owned by the Array and must not be disposed. /// internal void GetVariantBorrowElementAt(int index, out godot_variant elem) { if (index < 0 || index >= Count) throw new IndexOutOfRangeException(); GetVariantBorrowElementAtUnchecked(index, out elem); } /// /// The variant returned via the parameter is owned by the Array and must not be disposed. /// internal unsafe void GetVariantBorrowElementAtUnchecked(int index, out godot_variant elem) { elem = (*NativeValue._p)._arrayVector._ptr[index]; } } internal interface IGenericGodotArray { Array UnderlyingArray { get; } Type TypeOfElements { get; } } // TODO: Now we should be able to avoid boxing /// /// Typed wrapper around Godot's Array class, an array of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. Otherwise prefer .NET collections /// such as arrays or . /// /// The type of the array. [SuppressMessage("ReSharper", "RedundantExtendsListEntry")] public sealed class Array : IList, ICollection, IEnumerable, IGenericGodotArray { private readonly Array _underlyingArray; // 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 Type TypeOfElements = typeof(T); // ReSharper restore StaticMemberInGenericType Array IGenericGodotArray.UnderlyingArray => _underlyingArray; Type IGenericGodotArray.TypeOfElements => TypeOfElements; /// /// Constructs a new empty . /// public Array() { _underlyingArray = new Array(); } /// /// Constructs a new from the given collection's elements. /// /// The collection of elements to construct from. /// A new Godot Array. public Array(IEnumerable collection) { if (collection == null) throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); _underlyingArray = new Array(collection); } /// /// Constructs a new from the given items. /// /// The items to put in the new array. /// A new Godot Array. public Array(params T[] array) : this() { if (array == null) { throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); } _underlyingArray = new Array(array); } /// /// Constructs a typed from an untyped . /// /// The untyped array to construct from. public Array(Array array) { _underlyingArray = array; } // Explicit name to make it very clear internal static Array CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn) => new Array(Array.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); /// /// Converts this typed to an untyped . /// /// The typed array to convert. public static explicit operator Array(Array from) { return from._underlyingArray; } /// /// Duplicates this . /// /// If true, performs a deep copy. /// A new Godot Array. public Array Duplicate(bool deep = false) { return new Array(_underlyingArray.Duplicate(deep)); } /// /// Resizes this to the given size. /// /// The new size of the array. /// if successful, or an error code. public Error Resize(int newSize) { return _underlyingArray.Resize(newSize); } /// /// Shuffles the contents of this into a random order. /// public void Shuffle() { _underlyingArray.Shuffle(); } /// /// Concatenates these two s. /// /// The first array. /// The second array. /// A new Godot Array with the contents of both arrays. public static Array operator +(Array left, Array right) { return new Array(left._underlyingArray + right._underlyingArray); } // IList /// /// Returns the value at the given index. /// /// The value at the given index. public T this[int index] { get { _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); unsafe { return (T)Marshaling.variant_to_mono_object_of_type(&borrowElem, TypeOfElements); } } set => _underlyingArray[index] = value; } /// /// Searches this for an item /// and returns its index or -1 if not found. /// /// The item to search for. /// The index of the item, or -1 if not found. public int IndexOf(T item) { return _underlyingArray.IndexOf(item); } /// /// Inserts a new item at a given position in the . /// The position must be a valid position of an existing item, /// or the position at the end of the array. /// Existing items will be moved to the right. /// /// The index to insert at. /// The item to insert. public void Insert(int index, T item) { _underlyingArray.Insert(index, item); } /// /// Removes an element from this by index. /// /// The index of the element to remove. public void RemoveAt(int index) { _underlyingArray.RemoveAt(index); } // ICollection /// /// Returns the number of elements in this . /// This is also known as the size or length of the array. /// /// The number of elements. public int Count => _underlyingArray.Count; bool ICollection.IsReadOnly => false; /// /// Adds an item to the end of this . /// This is the same as `append` or `push_back` in GDScript. /// /// The item to add. /// The new size after adding the item. public void Add(T item) { _underlyingArray.Add(item); } /// /// Erases all items from this . /// public void Clear() { _underlyingArray.Clear(); } /// /// Checks if this contains the given item. /// /// The item to look for. /// Whether or not this array contains the given item. public bool Contains(T item) { return _underlyingArray.Contains(item); } /// /// Copies the elements of this to the given /// C# array, starting at the given index. /// /// The C# array to copy to. /// The index to start at. public void CopyTo(T[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); int count = _underlyingArray.Count; if (array.Length < (arrayIndex + count)) throw new ArgumentException( "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); for (int i = 0; i < count; i++) { array[arrayIndex] = this[i]; arrayIndex++; } } /// /// Removes the first occurrence of the specified value /// from this . /// /// The value to remove. /// A bool indicating success or failure. public bool Remove(T item) { int index = IndexOf(item); if (index >= 0) { RemoveAt(index); return true; } return false; } // IEnumerable /// /// Gets an enumerator for this . /// /// An enumerator. public IEnumerator GetEnumerator() { int count = _underlyingArray.Count; for (int i = 0; i < count; i++) { yield return this[i]; } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Converts this to a string. /// /// A string representation of this array. public override string ToString() => _underlyingArray.ToString(); } }