Sin descripción
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ReadOnlyArray.cs 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. ////REVIEW: switch to something that doesn't require the backing store to be an actual array?
  5. //// (maybe switch m_Array to an InlinedArray and extend InlinedArray to allow having three configs:
  6. //// 1. firstValue only, 2. firstValue + additionalValues, 3. everything in additionalValues)
  7. namespace UnityEngine.InputSystem.Utilities
  8. {
  9. /// <summary>
  10. /// Read-only access to an array or to a slice of an array.
  11. /// </summary>
  12. /// <typeparam name="TValue">Type of values stored in the array.</typeparam>
  13. /// <remarks>
  14. /// The purpose of this struct is to allow exposing internal arrays directly such that no
  15. /// boxing and no going through interfaces is required but at the same time not allowing
  16. /// the internal arrays to be modified.
  17. ///
  18. /// It differs from <c>ReadOnlySpan&lt;T&gt;</c> in that it can be stored on the heap and differs
  19. /// from <c>ReadOnlyCollection&lt;T&gt;</c> in that it supports slices directly without needing
  20. /// an intermediate object representing the slice.
  21. ///
  22. /// Note that in most cases, the ReadOnlyArray instance should be treated as a <em>temporary</em>.
  23. /// The actual array referred to by a ReadOnlyArray instance is usually owned and probably mutated
  24. /// by another piece of code. When that code makes changes to the array, the ReadOnlyArray
  25. /// instance will not get updated.
  26. /// </remarks>
  27. public struct ReadOnlyArray<TValue> : IReadOnlyList<TValue>
  28. {
  29. internal TValue[] m_Array;
  30. internal int m_StartIndex;
  31. internal int m_Length;
  32. /// <summary>
  33. /// Construct a read-only array covering all of the given array.
  34. /// </summary>
  35. /// <param name="array">Array to index.</param>
  36. public ReadOnlyArray(TValue[] array)
  37. {
  38. m_Array = array;
  39. m_StartIndex = 0;
  40. m_Length = array?.Length ?? 0;
  41. }
  42. /// <summary>
  43. /// Construct a read-only array that covers only the given slice of <paramref name="array"/>.
  44. /// </summary>
  45. /// <param name="array">Array to index.</param>
  46. /// <param name="index">Index at which to start indexing <paramref name="array"/>. The given element
  47. /// becomes index #0 for the read-only array.</param>
  48. /// <param name="length">Length of the slice to index from <paramref name="array"/>.</param>
  49. public ReadOnlyArray(TValue[] array, int index, int length)
  50. {
  51. m_Array = array;
  52. m_StartIndex = index;
  53. m_Length = length;
  54. }
  55. /// <summary>
  56. /// Convert to array.
  57. /// </summary>
  58. /// <returns>A new array containing a copy of the contents of the read-only array.</returns>
  59. public TValue[] ToArray()
  60. {
  61. var result = new TValue[m_Length];
  62. if (m_Length > 0)
  63. Array.Copy(m_Array, m_StartIndex, result, 0, m_Length);
  64. return result;
  65. }
  66. /// <summary>
  67. /// Searches for the first element in the array for which the given <paramref name="predicate"/> is <c>true</c> and returns the index of that element.
  68. /// </summary>
  69. /// <param name="predicate">The predicate to be evaluated for each element which defines the condition for the search.</param>
  70. /// <returns>Index of the first element for which <paramref name="predicate"/> is <c>true</c>x or -1 if no such element exists.</returns>
  71. /// <exception cref="ArgumentNullException">If predicate is <c>null</c>.</exception>
  72. /// <example>
  73. /// <code>
  74. /// // Searches for the first element in an integer array that is greater or equal to 5.
  75. /// var haystack = new ReadOnlyArray&lt;int&gt;(new[] { 1, 2, 3, 4, 5, 6, 7 });
  76. /// var index = haystack.IndexOf((value) => value >= 5); // index == 4
  77. /// </code>
  78. /// </example>
  79. public int IndexOf(Predicate<TValue> predicate)
  80. {
  81. if (predicate == null)
  82. throw new ArgumentNullException(nameof(predicate));
  83. for (var i = 0; i < m_Length; ++i)
  84. if (predicate(m_Array[m_StartIndex + i]))
  85. return i;
  86. return -1;
  87. }
  88. /// <summary>
  89. /// Returns an enumerator that iterates through the read-only array.
  90. /// <returns>
  91. /// <see cref="ReadOnlyArray{TValue}.Enumerator"/>
  92. /// An enumerator for the read-only array.
  93. /// </returns>
  94. /// </summary>
  95. public Enumerator GetEnumerator()
  96. {
  97. return new Enumerator(m_Array, m_StartIndex, m_Length);
  98. }
  99. /// <inheritdoc/>
  100. IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
  101. {
  102. return GetEnumerator();
  103. }
  104. /// <inheritdoc/>
  105. IEnumerator IEnumerable.GetEnumerator()
  106. {
  107. return GetEnumerator();
  108. }
  109. /// <summary>
  110. /// Constructs a read-only array containing elements <paramref name="array"/>.
  111. /// </summary>
  112. /// <param name="array">An existing array containing elements to be wrapped as a read-only array.</param>
  113. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "`ToXXX` message only really makes sense as static, which is not recommended for generic types.")]
  114. public static implicit operator ReadOnlyArray<TValue>(TValue[] array)
  115. {
  116. return new ReadOnlyArray<TValue>(array);
  117. }
  118. /// <summary>
  119. /// Number of elements in the array.
  120. /// </summary>
  121. public int Count => m_Length;
  122. /// <summary>
  123. /// Return the element at the given index.
  124. /// </summary>
  125. /// <param name="index">Index into the array.</param>
  126. /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is less than 0 or greater than <see cref="Count"/>.</exception>
  127. /// <exception cref="InvalidOperationException"></exception>
  128. public TValue this[int index]
  129. {
  130. get
  131. {
  132. if (index < 0 || index >= m_Length)
  133. throw new ArgumentOutOfRangeException(nameof(index));
  134. // We allow array to be null as we are patching up ReadOnlyArrays in a separate
  135. // path in several places.
  136. if (m_Array == null)
  137. throw new InvalidOperationException();
  138. return m_Array[m_StartIndex + index];
  139. }
  140. }
  141. /// <summary>
  142. /// <see cref="ReadOnlyArray{TValue}"/>
  143. /// Enumerates the elements of a read-only array.
  144. /// </summary>
  145. public struct Enumerator : IEnumerator<TValue>
  146. {
  147. private readonly TValue[] m_Array;
  148. private readonly int m_IndexStart;
  149. private readonly int m_IndexEnd;
  150. private int m_Index;
  151. internal Enumerator(TValue[] array, int index, int length)
  152. {
  153. m_Array = array;
  154. m_IndexStart = index - 1; // First call to MoveNext() moves us to first valid index.
  155. m_IndexEnd = index + length;
  156. m_Index = m_IndexStart;
  157. }
  158. /// <inheritdoc/>
  159. public void Dispose()
  160. {
  161. }
  162. /// <inheritdoc/>
  163. public bool MoveNext()
  164. {
  165. if (m_Index < m_IndexEnd)
  166. ++m_Index;
  167. return m_Index != m_IndexEnd;
  168. }
  169. /// <inheritdoc/>
  170. public void Reset()
  171. {
  172. m_Index = m_IndexStart;
  173. }
  174. /// <inheritdoc/>
  175. public TValue Current
  176. {
  177. get
  178. {
  179. if (m_Index == m_IndexEnd)
  180. throw new InvalidOperationException("Iterated beyond end");
  181. return m_Array[m_Index];
  182. }
  183. }
  184. object IEnumerator.Current => Current;
  185. }
  186. }
  187. /// <summary>
  188. /// Extension methods to help with <see cref="ReadOnlyArrayExtensions"/> contents.
  189. /// </summary>
  190. public static class ReadOnlyArrayExtensions
  191. {
  192. /// <summary>
  193. /// Evaluates whether <paramref name="array"/> contains an element that compares equal to <paramref name="value"/>.
  194. /// </summary>
  195. /// <typeparam name="TValue">The array element type.</typeparam>
  196. /// <param name="array">Reference to the read-only array to be searched.</param>
  197. /// <param name="value">The value to be searched for in <paramref name="array"/>.</param>
  198. /// <returns><c>true</c> if <paramref name="array"/> contains <paramref name="value"/>, else <c>false</c>.</returns>
  199. public static bool Contains<TValue>(this ReadOnlyArray<TValue> array, TValue value)
  200. where TValue : IComparable<TValue>
  201. {
  202. for (var i = 0; i < array.m_Length; ++i)
  203. if (array.m_Array[array.m_StartIndex + i].CompareTo(value) == 0)
  204. return true;
  205. return false;
  206. }
  207. /// <summary>
  208. /// Evaluates whether <paramref name="array"/> contains a reference to <paramref name="value"/>.
  209. /// </summary>
  210. /// <typeparam name="TValue">The array element type.</typeparam>
  211. /// <param name="array">Reference to the read-only array to be searched.</param>
  212. /// <param name="value">The reference to be searched for in <paramref name="array"/>.</param>
  213. /// <returns><c>true</c> if <paramref name="array"/> contains a reference to <paramref name="value"/>, else <c>false</c>.</returns>
  214. public static bool ContainsReference<TValue>(this ReadOnlyArray<TValue> array, TValue value)
  215. where TValue : class
  216. {
  217. return IndexOfReference(array, value) != -1;
  218. }
  219. /// <summary>
  220. /// Retrieves the index of <paramref name="value"/> in <paramref name="array"/>.
  221. /// </summary>
  222. /// <typeparam name="TValue">The array element type.</typeparam>
  223. /// <param name="array">Reference to the read-only array to be searched.</param>
  224. /// <param name="value">The reference to be searched for in <paramref name="array"/>.</param>
  225. /// <returns>The zero-based index of element <paramref name="value"/> in <paramref name="array"/> if such a reference could be found and -1 if it do not exist.</returns>
  226. public static int IndexOfReference<TValue>(this ReadOnlyArray<TValue> array, TValue value)
  227. where TValue : class
  228. {
  229. for (var i = 0; i < array.m_Length; ++i)
  230. if (ReferenceEquals(array.m_Array[array.m_StartIndex + i], value))
  231. return i;
  232. return -1;
  233. }
  234. /// <summary>
  235. /// Evaluates whether whether <paramref name="array1"/> and <paramref name="array2"/> contain the same sequence of references.
  236. /// </summary>
  237. /// <typeparam name="TValue">The array element type.</typeparam>
  238. /// <param name="array1">The first array to be evaluated.</param>
  239. /// <param name="array2">The second array to be evaluated.</param>
  240. /// <param name="count">The maximum number of elements to be compared.</param>
  241. /// <returns><c>true</c> if the <paramref name="count"/> first elements of <paramref name="array1"/> and <paramref name="array2"/> contain the same references, else false.</returns>
  242. internal static bool HaveEqualReferences<TValue>(this ReadOnlyArray<TValue> array1, IReadOnlyList<TValue> array2, int count = int.MaxValue)
  243. {
  244. var length1 = Math.Min(array1.Count, count);
  245. var length2 = Math.Min(array2.Count, count);
  246. if (length1 != length2)
  247. return false;
  248. for (var i = 0; i < length1; ++i)
  249. if (!ReferenceEquals(array1[i], array2[i]))
  250. return false;
  251. return true;
  252. }
  253. }
  254. }