A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using Unity.Collections;
5using Unity.Collections.LowLevel.Unsafe;
6
7namespace UnityEngine.InputSystem.Utilities
8{
9 /// <summary>
10 /// A collection of utility functions for working with arrays.
11 /// </summary>
12 /// <remarks>
13 /// The goal of this collection is to make it easy to use arrays directly rather than resorting to
14 /// <see cref="List{T}"/>.
15 /// </remarks>
16 internal static class ArrayHelpers
17 {
18 public static int LengthSafe<TValue>(this TValue[] array)
19 {
20 if (array == null)
21 return 0;
22 return array.Length;
23 }
24
25 public static void Clear<TValue>(this TValue[] array)
26 {
27 if (array == null)
28 return;
29
30 Array.Clear(array, 0, array.Length);
31 }
32
33 public static void Clear<TValue>(this TValue[] array, int count)
34 {
35 if (array == null)
36 return;
37 Array.Clear(array, 0, count);
38 }
39
40 public static void Clear<TValue>(this TValue[] array, ref int count)
41 {
42 if (array == null)
43 return;
44
45 Array.Clear(array, 0, count);
46 count = 0;
47 }
48
49 public static void EnsureCapacity<TValue>(ref TValue[] array, int count, int capacity, int capacityIncrement = 10)
50 {
51 if (capacity == 0)
52 return;
53
54 if (array == null)
55 {
56 array = new TValue[Math.Max(capacity, capacityIncrement)];
57 return;
58 }
59
60 var currentCapacity = array.Length - count;
61 if (currentCapacity >= capacity)
62 return;
63
64 DuplicateWithCapacity(ref array, count, capacity, capacityIncrement);
65 }
66
67 public static void DuplicateWithCapacity<TValue>(ref TValue[] array, int count, int capacity, int capacityIncrement = 10)
68 {
69 if (array == null)
70 {
71 array = new TValue[Math.Max(capacity, capacityIncrement)];
72 return;
73 }
74
75 var newSize = count + Math.Max(capacity, capacityIncrement);
76 var newArray = new TValue[newSize];
77 Array.Copy(array, newArray, count);
78 array = newArray;
79 }
80
81 public static bool Contains<TValue>(TValue[] array, TValue value)
82 {
83 if (array == null)
84 return false;
85
86 var comparer = EqualityComparer<TValue>.Default;
87 for (var i = 0; i < array.Length; ++i)
88 if (comparer.Equals(array[i], value))
89 return true;
90
91 return false;
92 }
93
94 public static bool ContainsReference<TValue>(this TValue[] array, TValue value)
95 where TValue : class
96 {
97 if (array == null)
98 return false;
99
100 return ContainsReference(array, array.Length, value);
101 }
102
103 public static bool ContainsReference<TFirst, TSecond>(this TFirst[] array, int count, TSecond value)
104 where TSecond : class
105 where TFirst : TSecond
106 {
107 return IndexOfReference(array, value, count) != -1;
108 }
109
110 public static bool ContainsReference<TFirst, TSecond>(this TFirst[] array, int startIndex, int count, TSecond value)
111 where TSecond : class
112 where TFirst : TSecond
113 {
114 return IndexOfReference(array, value, startIndex, count) != -1;
115 }
116
117 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Keep this for future implementation")]
118 public static bool HaveDuplicateReferences<TFirst>(this TFirst[] first, int index, int count)
119 {
120 for (var i = 0; i < count; ++i)
121 {
122 var element = first[i];
123 for (var n = i + 1; n < count - i; ++n)
124 {
125 if (ReferenceEquals(element, first[n]))
126 return true;
127 }
128 }
129 return false;
130 }
131
132 public static bool HaveEqualElements<TValue>(TValue[] first, TValue[] second, int count = int.MaxValue)
133 {
134 if (first == null || second == null)
135 return second == first;
136
137 var lengthFirst = Math.Min(count, first.Length);
138 var lengthSecond = Math.Min(count, second.Length);
139
140 if (lengthFirst != lengthSecond)
141 return false;
142
143 var comparer = EqualityComparer<TValue>.Default;
144 for (var i = 0; i < lengthFirst; ++i)
145 if (!comparer.Equals(first[i], second[i]))
146 return false;
147
148 return true;
149 }
150
151 ////REVIEW: remove this to get rid of default equality comparer?
152 public static int IndexOf<TValue>(TValue[] array, TValue value, int startIndex = 0, int count = -1)
153 {
154 if (array == null)
155 return -1;
156
157 if (count < 0)
158 count = array.Length - startIndex;
159 var comparer = EqualityComparer<TValue>.Default;
160 for (var i = startIndex; i < startIndex + count; ++i)
161 if (comparer.Equals(array[i], value))
162 return i;
163
164 return -1;
165 }
166
167 public static int IndexOf<TValue>(this TValue[] array, Predicate<TValue> predicate)
168 {
169 if (array == null)
170 return -1;
171
172 var length = array.Length;
173 for (var i = 0; i < length; ++i)
174 if (predicate(array[i]))
175 return i;
176
177 return -1;
178 }
179
180 public static int IndexOf<TValue>(this TValue[] array, Predicate<TValue> predicate, int startIndex = 0, int count = -1)
181 {
182 if (array == null)
183 return -1;
184
185 var end = startIndex + (count < 0 ? array.Length - startIndex : count);
186 for (var i = startIndex; i < end; ++i)
187 {
188 if (predicate(array[i]))
189 return i;
190 }
191
192 return -1;
193 }
194
195 public static int IndexOfReference<TFirst, TSecond>(this TFirst[] array, TSecond value, int count = -1)
196 where TSecond : class
197 where TFirst : TSecond
198 {
199 return IndexOfReference(array, value, 0, count);
200 }
201
202 public static int IndexOfReference<TFirst, TSecond>(this TFirst[] array, TSecond value, int startIndex, int count)
203 where TSecond : class
204 where TFirst : TSecond
205 {
206 if (array == null)
207 return -1;
208
209 if (count < 0)
210 count = array.Length - startIndex;
211 for (var i = startIndex; i < startIndex + count; ++i)
212 if (ReferenceEquals(array[i], value))
213 return i;
214
215 return -1;
216 }
217
218 public static int IndexOfValue<TValue>(this TValue[] array, TValue value, int startIndex = 0, int count = -1)
219 where TValue : struct, IEquatable<TValue>
220 {
221 if (array == null)
222 return -1;
223
224 if (count < 0)
225 count = array.Length - startIndex;
226 for (var i = startIndex; i < startIndex + count; ++i)
227 if (value.Equals(array[i]))
228 return i;
229
230 return -1;
231 }
232
233 public static unsafe void Resize<TValue>(ref NativeArray<TValue> array, int newSize, Allocator allocator)
234 where TValue : struct
235 {
236 var oldSize = array.Length;
237 if (oldSize == newSize)
238 return;
239
240 if (newSize == 0)
241 {
242 if (array.IsCreated)
243 array.Dispose();
244 array = new NativeArray<TValue>();
245 return;
246 }
247
248 var newArray = new NativeArray<TValue>(newSize, allocator);
249 if (oldSize != 0)
250 {
251 // Copy contents from old array.
252 UnsafeUtility.MemCpy(newArray.GetUnsafePtr(), array.GetUnsafeReadOnlyPtr(),
253 UnsafeUtility.SizeOf<TValue>() * (newSize < oldSize ? newSize : oldSize));
254 array.Dispose();
255 }
256 array = newArray;
257 }
258
259 public static int Append<TValue>(ref TValue[] array, TValue value)
260 {
261 if (array == null)
262 {
263 array = new TValue[1];
264 array[0] = value;
265 return 0;
266 }
267
268 var length = array.Length;
269 Array.Resize(ref array, length + 1);
270 array[length] = value;
271 return length;
272 }
273
274 public static int Append<TValue>(ref TValue[] array, IEnumerable<TValue> values)
275 {
276 if (array == null)
277 {
278 array = values.ToArray();
279 return 0;
280 }
281
282 var oldLength = array.Length;
283 var valueCount = values.Count();
284
285 Array.Resize(ref array, oldLength + valueCount);
286
287 var index = oldLength;
288 foreach (var value in values)
289 array[index++] = value;
290
291 return oldLength;
292 }
293
294 // Append to an array that is considered immutable. This allows using 'values' as is
295 // if 'array' is null.
296 // Returns the index of the first newly added element in the resulting array.
297 public static int AppendToImmutable<TValue>(ref TValue[] array, TValue[] values)
298 {
299 if (array == null)
300 {
301 array = values;
302 return 0;
303 }
304
305 if (values != null && values.Length > 0)
306 {
307 var oldCount = array.Length;
308 var valueCount = values.Length;
309 Array.Resize(ref array, oldCount + valueCount);
310 Array.Copy(values, 0, array, oldCount, valueCount);
311 return oldCount;
312 }
313
314 return array.Length;
315 }
316
317 public static int AppendWithCapacity<TValue>(ref TValue[] array, ref int count, TValue value, int capacityIncrement = 10)
318 {
319 if (array == null)
320 {
321 array = new TValue[capacityIncrement];
322 array[0] = value;
323 ++count;
324 return 0;
325 }
326
327 var capacity = array.Length;
328 if (capacity == count)
329 {
330 capacity += capacityIncrement;
331 Array.Resize(ref array, capacity);
332 }
333
334 var index = count;
335 array[index] = value;
336 ++count;
337
338 return index;
339 }
340
341 public static int AppendListWithCapacity<TValue, TValues>(ref TValue[] array, ref int length, TValues values, int capacityIncrement = 10)
342 where TValues : IReadOnlyList<TValue>
343 {
344 var numToAdd = values.Count;
345 if (array == null)
346 {
347 var size = Math.Max(numToAdd, capacityIncrement);
348 array = new TValue[size];
349 for (var i = 0; i < numToAdd; ++i)
350 array[i] = values[i];
351 length += numToAdd;
352 return 0;
353 }
354
355 var capacity = array.Length;
356 if (capacity < length + numToAdd)
357 {
358 capacity += Math.Max(length + numToAdd, capacityIncrement);
359 Array.Resize(ref array, capacity);
360 }
361
362 var index = length;
363 for (var i = 0; i < numToAdd; ++i)
364 array[index + i] = values[i];
365 length += numToAdd;
366
367 return index;
368 }
369
370 public static int AppendWithCapacity<TValue>(ref NativeArray<TValue> array, ref int count, TValue value,
371 int capacityIncrement = 10, Allocator allocator = Allocator.Persistent)
372 where TValue : struct
373 {
374 var capacity = array.Length;
375 if (capacity == count)
376 GrowBy(ref array, capacityIncrement > 1 ? capacityIncrement : 1, allocator);
377
378 var index = count;
379 array[index] = value;
380 ++count;
381
382 return index;
383 }
384
385 public static void InsertAt<TValue>(ref TValue[] array, int index, TValue value)
386 {
387 if (array == null)
388 {
389 ////REVIEW: allow growing array to specific size by inserting at arbitrary index?
390 if (index != 0)
391 throw new ArgumentOutOfRangeException(nameof(index));
392
393 array = new TValue[1];
394 array[0] = value;
395 return;
396 }
397
398 // Reallocate.
399 var oldLength = array.Length;
400 Array.Resize(ref array, oldLength + 1);
401
402 // Make room for element.
403 if (index != oldLength)
404 Array.Copy(array, index, array, index + 1, oldLength - index);
405
406 array[index] = value;
407 }
408
409 public static void InsertAtWithCapacity<TValue>(ref TValue[] array, ref int count, int index, TValue value, int capacityIncrement = 10)
410 {
411 EnsureCapacity(ref array, count, count + 1, capacityIncrement);
412
413 if (index != count)
414 Array.Copy(array, index, array, index + 1, count - index);
415
416 array[index] = value;
417 ++count;
418 }
419
420 public static void PutAtIfNotSet<TValue>(ref TValue[] array, int index, Func<TValue> valueFn)
421 {
422 if (array.LengthSafe() < index + 1)
423 Array.Resize(ref array, index + 1);
424
425 if (EqualityComparer<TValue>.Default.Equals(array[index], default(TValue)))
426 array[index] = valueFn();
427 }
428
429 // Adds 'count' entries to the array. Returns first index of newly added entries.
430 public static int GrowBy<TValue>(ref TValue[] array, int count)
431 {
432 if (array == null)
433 {
434 array = new TValue[count];
435 return 0;
436 }
437
438 var oldLength = array.Length;
439 Array.Resize(ref array, oldLength + count);
440 return oldLength;
441 }
442
443 public static unsafe int GrowBy<TValue>(ref NativeArray<TValue> array, int count, Allocator allocator = Allocator.Persistent)
444 where TValue : struct
445 {
446 var length = array.Length;
447 if (length == 0)
448 {
449 array = new NativeArray<TValue>(count, allocator);
450 return 0;
451 }
452
453 var newArray = new NativeArray<TValue>(length + count, allocator);
454 // CopyFrom() expects length to match. Copy manually.
455 UnsafeUtility.MemCpy(newArray.GetUnsafePtr(), array.GetUnsafeReadOnlyPtr(), (long)length * UnsafeUtility.SizeOf<TValue>());
456 array.Dispose();
457 array = newArray;
458
459 return length;
460 }
461
462 public static int GrowWithCapacity<TValue>(ref TValue[] array, ref int count, int growBy, int capacityIncrement = 10)
463 {
464 var length = array != null ? array.Length : 0;
465 if (length < count + growBy)
466 {
467 if (capacityIncrement < growBy)
468 capacityIncrement = growBy;
469 GrowBy(ref array, capacityIncrement);
470 }
471
472 var offset = count;
473 count += growBy;
474 return offset;
475 }
476
477 public static int GrowWithCapacity<TValue>(ref NativeArray<TValue> array, ref int count, int growBy,
478 int capacityIncrement = 10, Allocator allocator = Allocator.Persistent)
479 where TValue : struct
480 {
481 var length = array.Length;
482 if (length < count + growBy)
483 {
484 if (capacityIncrement < growBy)
485 capacityIncrement = growBy;
486 GrowBy(ref array, capacityIncrement, allocator);
487 }
488
489 var offset = count;
490 count += growBy;
491 return offset;
492 }
493
494 public static TValue[] Join<TValue>(TValue value, params TValue[] values)
495 {
496 // Determine length.
497 var length = 0;
498 if (value != null)
499 ++length;
500 if (values != null)
501 length += values.Length;
502
503 if (length == 0)
504 return null;
505
506 var array = new TValue[length];
507
508 // Populate.
509 var index = 0;
510 if (value != null)
511 array[index++] = value;
512
513 if (values != null)
514 Array.Copy(values, 0, array, index, values.Length);
515
516 return array;
517 }
518
519 public static TValue[] Merge<TValue>(TValue[] first, TValue[] second)
520 where TValue : IEquatable<TValue>
521 {
522 if (first == null)
523 return second;
524 if (second == null)
525 return first;
526
527 var merged = new List<TValue>();
528 merged.AddRange(first);
529
530 for (var i = 0; i < second.Length; ++i)
531 {
532 var secondValue = second[i];
533 if (!merged.Exists(x => x.Equals(secondValue)))
534 {
535 merged.Add(secondValue);
536 }
537 }
538
539 return merged.ToArray();
540 }
541
542 public static TValue[] Merge<TValue>(TValue[] first, TValue[] second, IEqualityComparer<TValue> comparer)
543 {
544 if (first == null)
545 return second;
546 if (second == null)
547 return null;
548
549 var merged = new List<TValue>();
550 merged.AddRange(first);
551
552 for (var i = 0; i < second.Length; ++i)
553 {
554 var secondValue = second[i];
555 if (!merged.Exists(x => comparer.Equals(secondValue)))
556 {
557 merged.Add(secondValue);
558 }
559 }
560
561 return merged.ToArray();
562 }
563
564 public static void EraseAt<TValue>(ref TValue[] array, int index)
565 {
566 Debug.Assert(array != null);
567 Debug.Assert(index >= 0 && index < array.Length);
568
569 var length = array.Length;
570 if (index == 0 && length == 1)
571 {
572 array = null;
573 return;
574 }
575
576 if (index < length - 1)
577 Array.Copy(array, index + 1, array, index, length - index - 1);
578
579 Array.Resize(ref array, length - 1);
580 }
581
582 public static void EraseAtWithCapacity<TValue>(this TValue[] array, ref int count, int index)
583 {
584 Debug.Assert(array != null);
585 Debug.Assert(count <= array.Length);
586 Debug.Assert(index >= 0 && index < count);
587
588 // If we're erasing from the beginning or somewhere in the middle, move
589 // the array contents down from after the index.
590 if (index < count - 1)
591 {
592 Array.Copy(array, index + 1, array, index, count - index - 1);
593 }
594
595 array[count - 1] = default; // Tail has been moved down by one.
596 --count;
597 }
598
599 public static unsafe void EraseAtWithCapacity<TValue>(NativeArray<TValue> array, ref int count, int index)
600 where TValue : struct
601 {
602 Debug.Assert(array.IsCreated);
603 Debug.Assert(count <= array.Length);
604 Debug.Assert(index >= 0 && index < count);
605
606 // If we're erasing from the beginning or somewhere in the middle, move
607 // the array contents down from after the index.
608 if (index < count - 1)
609 {
610 var elementSize = UnsafeUtility.SizeOf<TValue>();
611 var arrayPtr = (byte*)array.GetUnsafePtr();
612
613 UnsafeUtility.MemCpy(arrayPtr + elementSize * index, arrayPtr + elementSize * (index + 1),
614 (count - index - 1) * elementSize);
615 }
616
617 --count;
618 }
619
620 public static bool Erase<TValue>(ref TValue[] array, TValue value)
621 {
622 var index = IndexOf(array, value);
623 if (index != -1)
624 {
625 EraseAt(ref array, index);
626 return true;
627 }
628 return false;
629 }
630
631 /// <summary>
632 /// Erase an element from the array by moving the tail element into its place.
633 /// </summary>
634 /// <param name="array">Array to modify. May be not <c>null</c>.</param>
635 /// <param name="count">Current number of elements inside of array. May be less than <c>array.Length</c>.</param>
636 /// <param name="index">Index of element to remove. Tail element will get moved into its place.</param>
637 /// <typeparam name="TValue"></typeparam>
638 /// <remarks>
639 /// This method does not re-allocate the array. Instead <paramref name="count"/> is used
640 /// to keep track of how many elements there actually are in the array.
641 /// </remarks>
642 public static void EraseAtByMovingTail<TValue>(TValue[] array, ref int count, int index)
643 {
644 Debug.Assert(array != null);
645 Debug.Assert(index >= 0 && index < array.Length);
646 Debug.Assert(count >= 0 && count <= array.Length);
647 Debug.Assert(index < count);
648
649 // Move tail, if necessary.
650 if (index != count - 1)
651 array[index] = array[count - 1];
652
653 // Destroy current tail.
654 if (count >= 1)
655 array[count - 1] = default;
656 --count;
657 }
658
659 public static TValue[] Copy<TValue>(TValue[] array)
660 {
661 if (array == null)
662 return null;
663
664 var length = array.Length;
665 var result = new TValue[length];
666 Array.Copy(array, result, length);
667 return result;
668 }
669
670 public static TValue[] Clone<TValue>(TValue[] array)
671 where TValue : ICloneable
672 {
673 if (array == null)
674 return null;
675
676 var count = array.Length;
677 var result = new TValue[count];
678
679 for (var i = 0; i < count; ++i)
680 result[i] = (TValue)array[i].Clone();
681
682 return result;
683 }
684
685 public static TNew[] Select<TOld, TNew>(TOld[] array, Func<TOld, TNew> converter)
686 {
687 if (array == null)
688 return null;
689
690 var length = array.Length;
691 var result = new TNew[length];
692
693 for (var i = 0; i < length; ++i)
694 result[i] = converter(array[i]);
695
696 return result;
697 }
698
699 private static void Swap<TValue>(ref TValue first, ref TValue second)
700 {
701 var temp = first;
702 first = second;
703 second = temp;
704 }
705
706 /// <summary>
707 /// Move a slice in the array to a different place without allocating a temporary array.
708 /// </summary>
709 /// <param name="array"></param>
710 /// <param name="sourceIndex"></param>
711 /// <param name="destinationIndex"></param>
712 /// <param name="count"></param>
713 /// <typeparam name="TValue"></typeparam>
714 /// <remarks>
715 /// The slice is moved by repeatedly swapping slices until all the slices are where they
716 /// are supposed to go. This is not super efficient but avoids having to allocate a temporary
717 /// array on the heap.
718 /// </remarks>
719 public static void MoveSlice<TValue>(TValue[] array, int sourceIndex, int destinationIndex, int count)
720 {
721 if (count <= 0 || sourceIndex == destinationIndex)
722 return;
723
724 // Determine the number of elements in the window.
725 int elementCount;
726 if (destinationIndex > sourceIndex)
727 elementCount = destinationIndex + count - sourceIndex;
728 else
729 elementCount = sourceIndex + count - destinationIndex;
730
731 // If the source and target slice are right next to each other, just go
732 // and swap out the elements in both slices.
733 if (elementCount == count * 2)
734 {
735 for (var i = 0; i < count; ++i)
736 Swap(ref array[sourceIndex + i], ref array[destinationIndex + i]);
737 }
738 else
739 {
740 // There's elements in-between the two slices.
741 //
742 // The easiest way to picture this operation is as a rotation of the elements within
743 // the window given by sourceIndex, destination, and count. Within that window, we are
744 // simply treating it as a wrap-around buffer and then sliding the elements clockwise
745 // or counter-clockwise (depending on whether we move up or down, respectively) through
746 // the window.
747 //
748 // Unfortunately, we can't just memcopy the slices within that window as we have to
749 // have a temporary copy in place in order to preserve element values. So instead, we
750 // go and swap elements one by one, something that doesn't require anything other than
751 // a single value temporary copy.
752
753 // Determine the number of swaps we need to achieve the desired order. Swaps
754 // operate in pairs so it's one less than the number of elements in the range.
755 var swapCount = elementCount - 1;
756
757 // We simply take sourceIndex as fixed and do all swaps from there until all
758 // the elements in the window are in the right order. Each swap will put one
759 // element in its final place.
760 var dst = destinationIndex;
761 for (var i = 0; i < swapCount; ++i)
762 {
763 // Swap source into its destination place. This puts the current sourceIndex
764 // element in its final place.
765 Swap(ref array[dst], ref array[sourceIndex]);
766
767 // Find out where the element that we now swapped into sourceIndex should
768 // actually go.
769 if (destinationIndex > sourceIndex)
770 {
771 // Rotating clockwise.
772 dst -= count;
773 if (dst < sourceIndex)
774 dst = destinationIndex + count - Math.Abs(sourceIndex - dst); // Wrap around.
775 }
776 else
777 {
778 // Rotating counter-clockwise.
779 dst += count;
780 if (dst >= sourceIndex + count)
781 dst = destinationIndex + (dst - (sourceIndex + count)); // Wrap around.
782 }
783 }
784 }
785 }
786
787 public static void EraseSliceWithCapacity<TValue>(ref TValue[] array, ref int length, int index, int count)
788 {
789 // Move elements down.
790 if (count < length)
791 Array.Copy(array, index + count, array, index, length - index - count);
792
793 // Erase now vacant slots.
794 for (var i = 0; i < count; ++i)
795 array[length - i - 1] = default;
796
797 length -= count;
798 }
799
800 public static void SwapElements<TValue>(this TValue[] array, int index1, int index2)
801 {
802 MemoryHelpers.Swap(ref array[index1], ref array[index2]);
803 }
804
805 public static void SwapElements<TValue>(this NativeArray<TValue> array, int index1, int index2)
806 where TValue : struct
807 {
808 var temp = array[index1];
809 array[index1] = array[index2];
810 array[index2] = temp;
811 }
812 }
813}