A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5
6////REVIEW: what about ignoring 'firstValue' entirely in case length > 1 and putting everything into an array in that case
7
8namespace UnityEngine.InputSystem.Utilities
9{
10 /// <summary>
11 /// Helper to avoid array allocations if there's only a single value in the array.
12 /// </summary>
13 /// <remarks>
14 /// Also, once more than one entry is necessary, allows treating the extra array as having capacity.
15 /// This means that, for example, 5 or 10 entries can be allocated in batch rather than growing an
16 /// array one by one.
17 /// </remarks>
18 /// <typeparam name="TValue">Element type for the array.</typeparam>
19 internal struct InlinedArray<TValue> : IEnumerable<TValue>
20 {
21 // We inline the first value so if there's only one, there's
22 // no additional allocation. If more are added, we allocate an array.
23 public int length;
24 public TValue firstValue;
25 public TValue[] additionalValues;
26
27 public int Capacity => additionalValues?.Length + 1 ?? 1;
28
29 public InlinedArray(TValue value)
30 {
31 length = 1;
32 firstValue = value;
33 additionalValues = null;
34 }
35
36 public InlinedArray(TValue firstValue, params TValue[] additionalValues)
37 {
38 length = 1 + additionalValues.Length;
39 this.firstValue = firstValue;
40 this.additionalValues = additionalValues;
41 }
42
43 public InlinedArray(IEnumerable<TValue> values)
44 : this()
45 {
46 length = values.Count();
47 if (length > 1)
48 additionalValues = new TValue[length - 1];
49 else
50 additionalValues = null;
51
52 var index = 0;
53 foreach (var value in values)
54 {
55 if (index == 0)
56 firstValue = value;
57 else
58 additionalValues[index - 1] = value;
59 ++index;
60 }
61 }
62
63 public TValue this[int index]
64 {
65 get
66 {
67 if (index < 0 || index >= length)
68 throw new ArgumentOutOfRangeException(nameof(index));
69
70 if (index == 0)
71 return firstValue;
72
73 return additionalValues[index - 1];
74 }
75 set
76 {
77 if (index < 0 || index >= length)
78 throw new ArgumentOutOfRangeException(nameof(index));
79
80 if (index == 0)
81 firstValue = value;
82 else
83 additionalValues[index - 1] = value;
84 }
85 }
86
87 public void Clear()
88 {
89 length = 0;
90 firstValue = default;
91 additionalValues = null;
92 }
93
94 public void ClearWithCapacity()
95 {
96 firstValue = default;
97 for (var i = 0; i < length - 1; ++i)
98 additionalValues[i] = default;
99 length = 0;
100 }
101
102 ////REVIEW: This is inconsistent with ArrayHelpers.Clone() which also clones elements
103 public InlinedArray<TValue> Clone()
104 {
105 return new InlinedArray<TValue>
106 {
107 length = length,
108 firstValue = firstValue,
109 additionalValues = additionalValues != null ? ArrayHelpers.Copy(additionalValues) : null
110 };
111 }
112
113 public void SetLength(int size)
114 {
115 // Null out everything we're cutting off.
116 if (size < length)
117 {
118 for (var i = size; i < length; ++i)
119 this[i] = default;
120 }
121
122 length = size;
123
124 if (size > 1 && (additionalValues == null || additionalValues.Length < size - 1))
125 Array.Resize(ref additionalValues, size - 1);
126 }
127
128 public TValue[] ToArray()
129 {
130 return ArrayHelpers.Join(firstValue, additionalValues);
131 }
132
133 public TOther[] ToArray<TOther>(Func<TValue, TOther> mapFunction)
134 {
135 if (length == 0)
136 return null;
137
138 var result = new TOther[length];
139 for (var i = 0; i < length; ++i)
140 result[i] = mapFunction(this[i]);
141
142 return result;
143 }
144
145 public int IndexOf(TValue value)
146 {
147 var comparer = EqualityComparer<TValue>.Default;
148 if (length > 0)
149 {
150 if (comparer.Equals(firstValue, value))
151 return 0;
152 if (additionalValues != null)
153 {
154 for (var i = 0; i < length - 1; ++i)
155 if (comparer.Equals(additionalValues[i], value))
156 return i + 1;
157 }
158 }
159
160 return -1;
161 }
162
163 public int Append(TValue value)
164 {
165 if (length == 0)
166 {
167 firstValue = value;
168 }
169 else if (additionalValues == null)
170 {
171 additionalValues = new TValue[1];
172 additionalValues[0] = value;
173 }
174 else
175 {
176 Array.Resize(ref additionalValues, length);
177 additionalValues[length - 1] = value;
178 }
179
180 var index = length;
181 ++length;
182 return index;
183 }
184
185 public int AppendWithCapacity(TValue value, int capacityIncrement = 10)
186 {
187 if (length == 0)
188 {
189 firstValue = value;
190 }
191 else
192 {
193 var numAdditionalValues = length - 1;
194 ArrayHelpers.AppendWithCapacity(ref additionalValues, ref numAdditionalValues, value, capacityIncrement: capacityIncrement);
195 }
196
197 var index = length;
198 ++length;
199 return index;
200 }
201
202 public void AssignWithCapacity(InlinedArray<TValue> values)
203 {
204 if (Capacity < values.length && values.length > 1)
205 additionalValues = new TValue[values.length - 1];
206
207 length = values.length;
208 if (length > 0)
209 firstValue = values.firstValue;
210 if (length > 1)
211 Array.Copy(values.additionalValues, additionalValues, length - 1);
212 }
213
214 public void Append(IEnumerable<TValue> values)
215 {
216 foreach (var value in values)
217 Append(value);
218 }
219
220 public void Remove(TValue value)
221 {
222 if (length < 1)
223 return;
224
225 if (EqualityComparer<TValue>.Default.Equals(firstValue, value))
226 {
227 RemoveAt(0);
228 }
229 else if (additionalValues != null)
230 {
231 for (var i = 0; i < length - 1; ++i)
232 {
233 if (EqualityComparer<TValue>.Default.Equals(additionalValues[i], value))
234 {
235 RemoveAt(i + 1);
236 break;
237 }
238 }
239 }
240 }
241
242 public void RemoveAtWithCapacity(int index)
243 {
244 if (index < 0 || index >= length)
245 throw new ArgumentOutOfRangeException(nameof(index));
246
247 if (index == 0)
248 {
249 if (length == 1)
250 {
251 firstValue = default;
252 }
253 else if (length == 2)
254 {
255 firstValue = additionalValues[0];
256 additionalValues[0] = default;
257 }
258 else
259 {
260 Debug.Assert(length > 2);
261 firstValue = additionalValues[0];
262 var numAdditional = length - 1;
263 ArrayHelpers.EraseAtWithCapacity(additionalValues, ref numAdditional, 0);
264 }
265 }
266 else
267 {
268 var numAdditional = length - 1;
269 ArrayHelpers.EraseAtWithCapacity(additionalValues, ref numAdditional, index - 1);
270 }
271
272 --length;
273 }
274
275 public void RemoveAt(int index)
276 {
277 if (index < 0 || index >= length)
278 throw new ArgumentOutOfRangeException(nameof(index));
279
280 if (index == 0)
281 {
282 if (additionalValues != null)
283 {
284 firstValue = additionalValues[0];
285 if (additionalValues.Length == 1)
286 additionalValues = null;
287 else
288 {
289 Array.Copy(additionalValues, 1, additionalValues, 0, additionalValues.Length - 1);
290 Array.Resize(ref additionalValues, additionalValues.Length - 1);
291 }
292 }
293 else
294 {
295 firstValue = default;
296 }
297 }
298 else
299 {
300 Debug.Assert(additionalValues != null);
301
302 var numAdditionalValues = length - 1;
303 if (numAdditionalValues == 1)
304 {
305 // Remove only entry in array.
306 additionalValues = null;
307 }
308 else if (index == length - 1)
309 {
310 // Remove entry at end.
311 Array.Resize(ref additionalValues, numAdditionalValues - 1);
312 }
313 else
314 {
315 // Remove entry at beginning or in middle by pasting together
316 // into a new array.
317 var newAdditionalValues = new TValue[numAdditionalValues - 1];
318 if (index >= 2)
319 {
320 // Copy elements before entry.
321 Array.Copy(additionalValues, 0, newAdditionalValues, 0, index - 1);
322 }
323
324 // Copy elements after entry. We already know that we're not removing
325 // the last entry so there have to be entries.
326 Array.Copy(additionalValues, index + 1 - 1, newAdditionalValues, index - 1,
327 length - index - 1);
328
329 additionalValues = newAdditionalValues;
330 }
331 }
332
333 --length;
334 }
335
336 public void RemoveAtByMovingTailWithCapacity(int index)
337 {
338 if (index < 0 || index >= length)
339 throw new ArgumentOutOfRangeException(nameof(index));
340
341 var numAdditionalValues = length - 1;
342 if (index == 0)
343 {
344 if (length > 1)
345 {
346 firstValue = additionalValues[numAdditionalValues - 1];
347 additionalValues[numAdditionalValues - 1] = default;
348 }
349 else
350 {
351 firstValue = default;
352 }
353 }
354 else
355 {
356 Debug.Assert(additionalValues != null);
357
358 ArrayHelpers.EraseAtByMovingTail(additionalValues, ref numAdditionalValues, index - 1);
359 }
360
361 --length;
362 }
363
364 public bool RemoveByMovingTailWithCapacity(TValue value)
365 {
366 var index = IndexOf(value);
367 if (index == -1)
368 return false;
369
370 RemoveAtByMovingTailWithCapacity(index);
371 return true;
372 }
373
374 public bool Contains(TValue value, IEqualityComparer<TValue> comparer)
375 {
376 for (var n = 0; n < length; ++n)
377 if (comparer.Equals(this[n], value))
378 return true;
379 return false;
380 }
381
382 public void Merge(InlinedArray<TValue> other)
383 {
384 var comparer = EqualityComparer<TValue>.Default;
385 for (var i = 0; i < other.length; ++i)
386 {
387 var value = other[i];
388 if (Contains(value, comparer))
389 continue;
390
391 ////FIXME: this is ugly as it repeatedly copies
392 Append(value);
393 }
394 }
395
396 public IEnumerator<TValue> GetEnumerator()
397 {
398 return new Enumerator { array = this, index = -1 };
399 }
400
401 IEnumerator IEnumerable.GetEnumerator()
402 {
403 return GetEnumerator();
404 }
405
406 private struct Enumerator : IEnumerator<TValue>
407 {
408 public InlinedArray<TValue> array;
409 public int index;
410
411 public bool MoveNext()
412 {
413 if (index >= array.length)
414 return false;
415 ++index;
416 return index < array.length;
417 }
418
419 public void Reset()
420 {
421 index = -1;
422 }
423
424 public TValue Current => array[index];
425 object IEnumerator.Current => Current;
426
427 public void Dispose()
428 {
429 }
430 }
431 }
432
433 internal static class InputArrayExtensions
434 {
435 public static int IndexOfReference<TValue>(this InlinedArray<TValue> array, TValue value)
436 where TValue : class
437 {
438 for (var i = 0; i < array.length; ++i)
439 if (ReferenceEquals(array[i], value))
440 return i;
441
442 return -1;
443 }
444
445 public static bool Contains<TValue>(this InlinedArray<TValue> array, TValue value)
446 {
447 for (var i = 0; i < array.length; ++i)
448 if (array[i].Equals(value))
449 return true;
450 return false;
451 }
452
453 public static bool ContainsReference<TValue>(this InlinedArray<TValue> array, TValue value)
454 where TValue : class
455 {
456 return IndexOfReference(array, value) != -1;
457 }
458 }
459}