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