2
0

Array.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Runtime.CompilerServices;
  6. using Godot.NativeInterop;
  7. namespace Godot.Collections
  8. {
  9. /// <summary>
  10. /// Wrapper around Godot's Array class, an array of Variant
  11. /// typed elements allocated in the engine in C++. Useful when
  12. /// interfacing with the engine. Otherwise prefer .NET collections
  13. /// such as <see cref="System.Array"/> or <see cref="List{T}"/>.
  14. /// </summary>
  15. public sealed class Array : IList, IDisposable
  16. {
  17. internal godot_array.movable NativeValue;
  18. /// <summary>
  19. /// Constructs a new empty <see cref="Array"/>.
  20. /// </summary>
  21. public Array()
  22. {
  23. NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new();
  24. }
  25. /// <summary>
  26. /// Constructs a new <see cref="Array"/> from the given collection's elements.
  27. /// </summary>
  28. /// <param name="collection">The collection of elements to construct from.</param>
  29. /// <returns>A new Godot Array.</returns>
  30. public Array(IEnumerable collection) : this()
  31. {
  32. if (collection == null)
  33. throw new ArgumentNullException(nameof(collection));
  34. foreach (object element in collection)
  35. Add(element);
  36. }
  37. // TODO: This must be removed. Lots of silent mistakes as it takes pretty much anything.
  38. /// <summary>
  39. /// Constructs a new <see cref="Array"/> from the given objects.
  40. /// </summary>
  41. /// <param name="array">The objects to put in the new array.</param>
  42. /// <returns>A new Godot Array.</returns>
  43. public Array(params object[] array) : this()
  44. {
  45. if (array == null)
  46. throw new ArgumentNullException(nameof(array));
  47. NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new();
  48. int length = array.Length;
  49. Resize(length);
  50. for (int i = 0; i < length; i++)
  51. this[i] = array[i];
  52. }
  53. private Array(godot_array nativeValueToOwn)
  54. {
  55. NativeValue = (godot_array.movable)(nativeValueToOwn.IsAllocated ?
  56. nativeValueToOwn :
  57. NativeFuncs.godotsharp_array_new());
  58. }
  59. // Explicit name to make it very clear
  60. internal static Array CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn)
  61. => new Array(nativeValueToOwn);
  62. ~Array()
  63. {
  64. Dispose(false);
  65. }
  66. /// <summary>
  67. /// Disposes of this <see cref="Array"/>.
  68. /// </summary>
  69. public void Dispose()
  70. {
  71. Dispose(true);
  72. GC.SuppressFinalize(this);
  73. }
  74. public void Dispose(bool disposing)
  75. {
  76. // Always dispose `NativeValue` even if disposing is true
  77. NativeValue.DangerousSelfRef.Dispose();
  78. }
  79. /// <summary>
  80. /// Duplicates this <see cref="Array"/>.
  81. /// </summary>
  82. /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param>
  83. /// <returns>A new Godot Array.</returns>
  84. public Array Duplicate(bool deep = false)
  85. {
  86. godot_array newArray;
  87. var self = (godot_array)NativeValue;
  88. NativeFuncs.godotsharp_array_duplicate(ref self, deep.ToGodotBool(), out newArray);
  89. return CreateTakingOwnershipOfDisposableValue(newArray);
  90. }
  91. /// <summary>
  92. /// Resizes this <see cref="Array"/> to the given size.
  93. /// </summary>
  94. /// <param name="newSize">The new size of the array.</param>
  95. /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns>
  96. public Error Resize(int newSize)
  97. {
  98. var self = (godot_array)NativeValue;
  99. return NativeFuncs.godotsharp_array_resize(ref self, newSize);
  100. }
  101. /// <summary>
  102. /// Shuffles the contents of this <see cref="Array"/> into a random order.
  103. /// </summary>
  104. public void Shuffle()
  105. {
  106. var self = (godot_array)NativeValue;
  107. NativeFuncs.godotsharp_array_shuffle(ref self);
  108. }
  109. /// <summary>
  110. /// Concatenates these two <see cref="Array"/>s.
  111. /// </summary>
  112. /// <param name="left">The first array.</param>
  113. /// <param name="right">The second array.</param>
  114. /// <returns>A new Godot Array with the contents of both arrays.</returns>
  115. public static Array operator +(Array left, Array right)
  116. {
  117. if (left == null)
  118. {
  119. if (right == null)
  120. return new Array();
  121. return right.Duplicate(deep: false);
  122. }
  123. if (right == null)
  124. return left.Duplicate(deep: false);
  125. int leftCount = left.Count;
  126. int rightCount = right.Count;
  127. Array newArray = left.Duplicate(deep: false);
  128. newArray.Resize(leftCount + rightCount);
  129. for (int i = 0; i < rightCount; i++)
  130. newArray[i + leftCount] = right[i];
  131. return newArray;
  132. }
  133. // IList
  134. bool IList.IsReadOnly => false;
  135. bool IList.IsFixedSize => false;
  136. /// <summary>
  137. /// Returns the object at the given <paramref name="index"/>.
  138. /// </summary>
  139. /// <value>The object at the given <paramref name="index"/>.</value>
  140. public unsafe object this[int index]
  141. {
  142. get
  143. {
  144. GetVariantBorrowElementAt(index, out godot_variant borrowElem);
  145. return Marshaling.ConvertVariantToManagedObject(borrowElem);
  146. }
  147. set
  148. {
  149. if (index < 0 || index >= Count)
  150. throw new ArgumentOutOfRangeException(nameof(index));
  151. var self = (godot_array)NativeValue;
  152. godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
  153. ptrw[index] = Marshaling.ConvertManagedObjectToVariant(value);
  154. }
  155. }
  156. /// <summary>
  157. /// Adds an object to the end of this <see cref="Array"/>.
  158. /// This is the same as <c>append</c> or <c>push_back</c> in GDScript.
  159. /// </summary>
  160. /// <param name="value">The object to add.</param>
  161. /// <returns>The new size after adding the object.</returns>
  162. public int Add(object value)
  163. {
  164. using godot_variant variantValue = Marshaling.ConvertManagedObjectToVariant(value);
  165. var self = (godot_array)NativeValue;
  166. return NativeFuncs.godotsharp_array_add(ref self, variantValue);
  167. }
  168. /// <summary>
  169. /// Checks if this <see cref="Array"/> contains the given object.
  170. /// </summary>
  171. /// <param name="value">The item to look for.</param>
  172. /// <returns>Whether or not this array contains the given object.</returns>
  173. public bool Contains(object value) => IndexOf(value) != -1;
  174. /// <summary>
  175. /// Erases all items from this <see cref="Array"/>.
  176. /// </summary>
  177. public void Clear() => Resize(0);
  178. /// <summary>
  179. /// Searches this <see cref="Array"/> for an object
  180. /// and returns its index or -1 if not found.
  181. /// </summary>
  182. /// <param name="value">The object to search for.</param>
  183. /// <returns>The index of the object, or -1 if not found.</returns>
  184. public int IndexOf(object value)
  185. {
  186. using godot_variant variantValue = Marshaling.ConvertManagedObjectToVariant(value);
  187. var self = (godot_array)NativeValue;
  188. return NativeFuncs.godotsharp_array_index_of(ref self, variantValue);
  189. }
  190. /// <summary>
  191. /// Inserts a new object at a given position in the array.
  192. /// The position must be a valid position of an existing item,
  193. /// or the position at the end of the array.
  194. /// Existing items will be moved to the right.
  195. /// </summary>
  196. /// <param name="index">The index to insert at.</param>
  197. /// <param name="value">The object to insert.</param>
  198. public void Insert(int index, object value)
  199. {
  200. if (index < 0 || index > Count)
  201. throw new ArgumentOutOfRangeException(nameof(index));
  202. using godot_variant variantValue = Marshaling.ConvertManagedObjectToVariant(value);
  203. var self = (godot_array)NativeValue;
  204. NativeFuncs.godotsharp_array_insert(ref self, index, variantValue);
  205. }
  206. /// <summary>
  207. /// Removes the first occurrence of the specified <paramref name="value"/>
  208. /// from this <see cref="Array"/>.
  209. /// </summary>
  210. /// <param name="value">The value to remove.</param>
  211. public void Remove(object value)
  212. {
  213. int index = IndexOf(value);
  214. if (index >= 0)
  215. RemoveAt(index);
  216. }
  217. /// <summary>
  218. /// Removes an element from this <see cref="Array"/> by index.
  219. /// </summary>
  220. /// <param name="index">The index of the element to remove.</param>
  221. public void RemoveAt(int index)
  222. {
  223. if (index < 0 || index > Count)
  224. throw new ArgumentOutOfRangeException(nameof(index));
  225. var self = (godot_array)NativeValue;
  226. NativeFuncs.godotsharp_array_remove_at(ref self, index);
  227. }
  228. // ICollection
  229. /// <summary>
  230. /// Returns the number of elements in this <see cref="Array"/>.
  231. /// This is also known as the size or length of the array.
  232. /// </summary>
  233. /// <returns>The number of elements.</returns>
  234. public int Count => NativeValue.DangerousSelfRef.Size;
  235. object ICollection.SyncRoot => this;
  236. bool ICollection.IsSynchronized => false;
  237. /// <summary>
  238. /// Copies the elements of this <see cref="Array"/> to the given
  239. /// untyped C# array, starting at the given index.
  240. /// </summary>
  241. /// <param name="array">The array to copy to.</param>
  242. /// <param name="index">The index to start at.</param>
  243. public void CopyTo(System.Array array, int index)
  244. {
  245. if (array == null)
  246. throw new ArgumentNullException(nameof(array), "Value cannot be null.");
  247. if (index < 0)
  248. {
  249. throw new ArgumentOutOfRangeException(nameof(index),
  250. "Number was less than the array's lower bound in the first dimension.");
  251. }
  252. int count = Count;
  253. if (array.Length < (index + count))
  254. {
  255. throw new ArgumentException(
  256. "Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
  257. }
  258. unsafe
  259. {
  260. for (int i = 0; i < count; i++)
  261. {
  262. object obj = Marshaling.ConvertVariantToManagedObject(NativeValue.DangerousSelfRef.Elements[i]);
  263. array.SetValue(obj, index);
  264. index++;
  265. }
  266. }
  267. }
  268. // IEnumerable
  269. /// <summary>
  270. /// Gets an enumerator for this <see cref="Array"/>.
  271. /// </summary>
  272. /// <returns>An enumerator.</returns>
  273. public IEnumerator GetEnumerator()
  274. {
  275. int count = Count;
  276. for (int i = 0; i < count; i++)
  277. {
  278. yield return this[i];
  279. }
  280. }
  281. /// <summary>
  282. /// Converts this <see cref="Array"/> to a string.
  283. /// </summary>
  284. /// <returns>A string representation of this array.</returns>
  285. public override string ToString()
  286. {
  287. var self = (godot_array)NativeValue;
  288. NativeFuncs.godotsharp_array_to_string(ref self, out godot_string str);
  289. using (str)
  290. return Marshaling.ConvertStringToManaged(str);
  291. }
  292. /// <summary>
  293. /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed.
  294. /// </summary>
  295. internal void GetVariantBorrowElementAt(int index, out godot_variant elem)
  296. {
  297. if (index < 0 || index >= Count)
  298. throw new ArgumentOutOfRangeException(nameof(index));
  299. GetVariantBorrowElementAtUnchecked(index, out elem);
  300. }
  301. /// <summary>
  302. /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed.
  303. /// </summary>
  304. internal unsafe void GetVariantBorrowElementAtUnchecked(int index, out godot_variant elem)
  305. {
  306. elem = NativeValue.DangerousSelfRef.Elements[index];
  307. }
  308. }
  309. internal interface IGenericGodotArray
  310. {
  311. Array UnderlyingArray { get; }
  312. Type TypeOfElements { get; }
  313. }
  314. // TODO: Now we should be able to avoid boxing
  315. /// <summary>
  316. /// Typed wrapper around Godot's Array class, an array of Variant
  317. /// typed elements allocated in the engine in C++. Useful when
  318. /// interfacing with the engine. Otherwise prefer .NET collections
  319. /// such as arrays or <see cref="List{T}"/>.
  320. /// </summary>
  321. /// <typeparam name="T">The type of the array.</typeparam>
  322. [SuppressMessage("ReSharper", "RedundantExtendsListEntry")]
  323. [SuppressMessage("Naming", "CA1710", MessageId = "Identifiers should have correct suffix")]
  324. public sealed class Array<T> : IList<T>, ICollection<T>, IEnumerable<T>, IGenericGodotArray
  325. {
  326. private readonly Array _underlyingArray;
  327. internal ref godot_array.movable NativeValue
  328. {
  329. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  330. get => ref _underlyingArray.NativeValue;
  331. }
  332. // ReSharper disable StaticMemberInGenericType
  333. // Warning is about unique static fields being created for each generic type combination:
  334. // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html
  335. // In our case this is exactly what we want.
  336. private static readonly Type TypeOfElements = typeof(T);
  337. // ReSharper restore StaticMemberInGenericType
  338. Array IGenericGodotArray.UnderlyingArray => _underlyingArray;
  339. Type IGenericGodotArray.TypeOfElements => TypeOfElements;
  340. /// <summary>
  341. /// Constructs a new empty <see cref="Array{T}"/>.
  342. /// </summary>
  343. public Array()
  344. {
  345. _underlyingArray = new Array();
  346. }
  347. /// <summary>
  348. /// Constructs a new <see cref="Array{T}"/> from the given collection's elements.
  349. /// </summary>
  350. /// <param name="collection">The collection of elements to construct from.</param>
  351. /// <returns>A new Godot Array.</returns>
  352. public Array(IEnumerable<T> collection)
  353. {
  354. if (collection == null)
  355. throw new ArgumentNullException(nameof(collection));
  356. _underlyingArray = new Array(collection);
  357. }
  358. /// <summary>
  359. /// Constructs a new <see cref="Array{T}"/> from the given items.
  360. /// </summary>
  361. /// <param name="array">The items to put in the new array.</param>
  362. /// <returns>A new Godot Array.</returns>
  363. public Array(params T[] array) : this()
  364. {
  365. if (array == null)
  366. throw new ArgumentNullException(nameof(array));
  367. _underlyingArray = new Array(array);
  368. }
  369. /// <summary>
  370. /// Constructs a typed <see cref="Array{T}"/> from an untyped <see cref="Array"/>.
  371. /// </summary>
  372. /// <param name="array">The untyped array to construct from.</param>
  373. public Array(Array array)
  374. {
  375. _underlyingArray = array;
  376. }
  377. // Explicit name to make it very clear
  378. internal static Array<T> CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn)
  379. => new Array<T>(Array.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn));
  380. /// <summary>
  381. /// Converts this typed <see cref="Array{T}"/> to an untyped <see cref="Array"/>.
  382. /// </summary>
  383. /// <param name="from">The typed array to convert.</param>
  384. public static explicit operator Array(Array<T> from)
  385. {
  386. return from?._underlyingArray;
  387. }
  388. /// <summary>
  389. /// Duplicates this <see cref="Array{T}"/>.
  390. /// </summary>
  391. /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param>
  392. /// <returns>A new Godot Array.</returns>
  393. public Array<T> Duplicate(bool deep = false)
  394. {
  395. return new Array<T>(_underlyingArray.Duplicate(deep));
  396. }
  397. /// <summary>
  398. /// Resizes this <see cref="Array{T}"/> to the given size.
  399. /// </summary>
  400. /// <param name="newSize">The new size of the array.</param>
  401. /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns>
  402. public Error Resize(int newSize)
  403. {
  404. return _underlyingArray.Resize(newSize);
  405. }
  406. /// <summary>
  407. /// Shuffles the contents of this <see cref="Array{T}"/> into a random order.
  408. /// </summary>
  409. public void Shuffle()
  410. {
  411. _underlyingArray.Shuffle();
  412. }
  413. /// <summary>
  414. /// Concatenates these two <see cref="Array{T}"/>s.
  415. /// </summary>
  416. /// <param name="left">The first array.</param>
  417. /// <param name="right">The second array.</param>
  418. /// <returns>A new Godot Array with the contents of both arrays.</returns>
  419. public static Array<T> operator +(Array<T> left, Array<T> right)
  420. {
  421. if (left == null)
  422. {
  423. if (right == null)
  424. return new Array<T>();
  425. return right.Duplicate(deep: false);
  426. }
  427. if (right == null)
  428. return left.Duplicate(deep: false);
  429. return new Array<T>(left._underlyingArray + right._underlyingArray);
  430. }
  431. // IList<T>
  432. /// <summary>
  433. /// Returns the value at the given <paramref name="index"/>.
  434. /// </summary>
  435. /// <value>The value at the given <paramref name="index"/>.</value>
  436. public T this[int index]
  437. {
  438. get
  439. {
  440. _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem);
  441. return (T)Marshaling.ConvertVariantToManagedObjectOfType(borrowElem, TypeOfElements);
  442. }
  443. set => _underlyingArray[index] = value;
  444. }
  445. /// <summary>
  446. /// Searches this <see cref="Array{T}"/> for an item
  447. /// and returns its index or -1 if not found.
  448. /// </summary>
  449. /// <param name="item">The item to search for.</param>
  450. /// <returns>The index of the item, or -1 if not found.</returns>
  451. public int IndexOf(T item)
  452. {
  453. return _underlyingArray.IndexOf(item);
  454. }
  455. /// <summary>
  456. /// Inserts a new item at a given position in the <see cref="Array{T}"/>.
  457. /// The position must be a valid position of an existing item,
  458. /// or the position at the end of the array.
  459. /// Existing items will be moved to the right.
  460. /// </summary>
  461. /// <param name="index">The index to insert at.</param>
  462. /// <param name="item">The item to insert.</param>
  463. public void Insert(int index, T item)
  464. {
  465. _underlyingArray.Insert(index, item);
  466. }
  467. /// <summary>
  468. /// Removes an element from this <see cref="Array{T}"/> by index.
  469. /// </summary>
  470. /// <param name="index">The index of the element to remove.</param>
  471. public void RemoveAt(int index)
  472. {
  473. _underlyingArray.RemoveAt(index);
  474. }
  475. // ICollection<T>
  476. /// <summary>
  477. /// Returns the number of elements in this <see cref="Array{T}"/>.
  478. /// This is also known as the size or length of the array.
  479. /// </summary>
  480. /// <returns>The number of elements.</returns>
  481. public int Count => _underlyingArray.Count;
  482. bool ICollection<T>.IsReadOnly => false;
  483. /// <summary>
  484. /// Adds an item to the end of this <see cref="Array{T}"/>.
  485. /// This is the same as <c>append</c> or <c>push_back</c> in GDScript.
  486. /// </summary>
  487. /// <param name="item">The item to add.</param>
  488. /// <returns>The new size after adding the item.</returns>
  489. public void Add(T item)
  490. {
  491. _underlyingArray.Add(item);
  492. }
  493. /// <summary>
  494. /// Erases all items from this <see cref="Array{T}"/>.
  495. /// </summary>
  496. public void Clear()
  497. {
  498. _underlyingArray.Clear();
  499. }
  500. /// <summary>
  501. /// Checks if this <see cref="Array{T}"/> contains the given item.
  502. /// </summary>
  503. /// <param name="item">The item to look for.</param>
  504. /// <returns>Whether or not this array contains the given item.</returns>
  505. public bool Contains(T item)
  506. {
  507. return _underlyingArray.Contains(item);
  508. }
  509. /// <summary>
  510. /// Copies the elements of this <see cref="Array{T}"/> to the given
  511. /// C# array, starting at the given index.
  512. /// </summary>
  513. /// <param name="array">The C# array to copy to.</param>
  514. /// <param name="arrayIndex">The index to start at.</param>
  515. public void CopyTo(T[] array, int arrayIndex)
  516. {
  517. if (array == null)
  518. throw new ArgumentNullException(nameof(array), "Value cannot be null.");
  519. if (arrayIndex < 0)
  520. throw new ArgumentOutOfRangeException(nameof(arrayIndex),
  521. "Number was less than the array's lower bound in the first dimension.");
  522. int count = _underlyingArray.Count;
  523. if (array.Length < (arrayIndex + count))
  524. throw new ArgumentException(
  525. "Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
  526. for (int i = 0; i < count; i++)
  527. {
  528. array[arrayIndex] = this[i];
  529. arrayIndex++;
  530. }
  531. }
  532. /// <summary>
  533. /// Removes the first occurrence of the specified value
  534. /// from this <see cref="Array{T}"/>.
  535. /// </summary>
  536. /// <param name="item">The value to remove.</param>
  537. /// <returns>A <see langword="bool"/> indicating success or failure.</returns>
  538. public bool Remove(T item)
  539. {
  540. int index = IndexOf(item);
  541. if (index >= 0)
  542. {
  543. RemoveAt(index);
  544. return true;
  545. }
  546. return false;
  547. }
  548. // IEnumerable<T>
  549. /// <summary>
  550. /// Gets an enumerator for this <see cref="Array{T}"/>.
  551. /// </summary>
  552. /// <returns>An enumerator.</returns>
  553. public IEnumerator<T> GetEnumerator()
  554. {
  555. int count = _underlyingArray.Count;
  556. for (int i = 0; i < count; i++)
  557. {
  558. yield return this[i];
  559. }
  560. }
  561. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  562. /// <summary>
  563. /// Converts this <see cref="Array{T}"/> to a string.
  564. /// </summary>
  565. /// <returns>A string representation of this array.</returns>
  566. public override string ToString() => _underlyingArray.ToString();
  567. }
  568. }