460 lines
14 KiB
C#
460 lines
14 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
////REVIEW: what about ignoring 'firstValue' entirely in case length > 1 and putting everything into an array in that case
|
|
|
|
namespace UnityEngine.InputSystem.Utilities
|
|
{
|
|
/// <summary>
|
|
/// Helper to avoid array allocations if there's only a single value in the array.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Also, once more than one entry is necessary, allows treating the extra array as having capacity.
|
|
/// This means that, for example, 5 or 10 entries can be allocated in batch rather than growing an
|
|
/// array one by one.
|
|
/// </remarks>
|
|
/// <typeparam name="TValue">Element type for the array.</typeparam>
|
|
internal struct InlinedArray<TValue> : IEnumerable<TValue>
|
|
{
|
|
// We inline the first value so if there's only one, there's
|
|
// no additional allocation. If more are added, we allocate an array.
|
|
public int length;
|
|
public TValue firstValue;
|
|
public TValue[] additionalValues;
|
|
|
|
public int Capacity => additionalValues?.Length + 1 ?? 1;
|
|
|
|
public InlinedArray(TValue value)
|
|
{
|
|
length = 1;
|
|
firstValue = value;
|
|
additionalValues = null;
|
|
}
|
|
|
|
public InlinedArray(TValue firstValue, params TValue[] additionalValues)
|
|
{
|
|
length = 1 + additionalValues.Length;
|
|
this.firstValue = firstValue;
|
|
this.additionalValues = additionalValues;
|
|
}
|
|
|
|
public InlinedArray(IEnumerable<TValue> values)
|
|
: this()
|
|
{
|
|
length = values.Count();
|
|
if (length > 1)
|
|
additionalValues = new TValue[length - 1];
|
|
else
|
|
additionalValues = null;
|
|
|
|
var index = 0;
|
|
foreach (var value in values)
|
|
{
|
|
if (index == 0)
|
|
firstValue = value;
|
|
else
|
|
additionalValues[index - 1] = value;
|
|
++index;
|
|
}
|
|
}
|
|
|
|
public TValue this[int index]
|
|
{
|
|
get
|
|
{
|
|
if (index < 0 || index >= length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
if (index == 0)
|
|
return firstValue;
|
|
|
|
return additionalValues[index - 1];
|
|
}
|
|
set
|
|
{
|
|
if (index < 0 || index >= length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
if (index == 0)
|
|
firstValue = value;
|
|
else
|
|
additionalValues[index - 1] = value;
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
length = 0;
|
|
firstValue = default;
|
|
additionalValues = null;
|
|
}
|
|
|
|
public void ClearWithCapacity()
|
|
{
|
|
firstValue = default;
|
|
for (var i = 0; i < length - 1; ++i)
|
|
additionalValues[i] = default;
|
|
length = 0;
|
|
}
|
|
|
|
////REVIEW: This is inconsistent with ArrayHelpers.Clone() which also clones elements
|
|
public InlinedArray<TValue> Clone()
|
|
{
|
|
return new InlinedArray<TValue>
|
|
{
|
|
length = length,
|
|
firstValue = firstValue,
|
|
additionalValues = additionalValues != null ? ArrayHelpers.Copy(additionalValues) : null
|
|
};
|
|
}
|
|
|
|
public void SetLength(int size)
|
|
{
|
|
// Null out everything we're cutting off.
|
|
if (size < length)
|
|
{
|
|
for (var i = size; i < length; ++i)
|
|
this[i] = default;
|
|
}
|
|
|
|
length = size;
|
|
|
|
if (size > 1 && (additionalValues == null || additionalValues.Length < size - 1))
|
|
Array.Resize(ref additionalValues, size - 1);
|
|
}
|
|
|
|
public TValue[] ToArray()
|
|
{
|
|
return ArrayHelpers.Join(firstValue, additionalValues);
|
|
}
|
|
|
|
public TOther[] ToArray<TOther>(Func<TValue, TOther> mapFunction)
|
|
{
|
|
if (length == 0)
|
|
return null;
|
|
|
|
var result = new TOther[length];
|
|
for (var i = 0; i < length; ++i)
|
|
result[i] = mapFunction(this[i]);
|
|
|
|
return result;
|
|
}
|
|
|
|
public int IndexOf(TValue value)
|
|
{
|
|
var comparer = EqualityComparer<TValue>.Default;
|
|
if (length > 0)
|
|
{
|
|
if (comparer.Equals(firstValue, value))
|
|
return 0;
|
|
if (additionalValues != null)
|
|
{
|
|
for (var i = 0; i < length - 1; ++i)
|
|
if (comparer.Equals(additionalValues[i], value))
|
|
return i + 1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public int Append(TValue value)
|
|
{
|
|
if (length == 0)
|
|
{
|
|
firstValue = value;
|
|
}
|
|
else if (additionalValues == null)
|
|
{
|
|
additionalValues = new TValue[1];
|
|
additionalValues[0] = value;
|
|
}
|
|
else
|
|
{
|
|
Array.Resize(ref additionalValues, length);
|
|
additionalValues[length - 1] = value;
|
|
}
|
|
|
|
var index = length;
|
|
++length;
|
|
return index;
|
|
}
|
|
|
|
public int AppendWithCapacity(TValue value, int capacityIncrement = 10)
|
|
{
|
|
if (length == 0)
|
|
{
|
|
firstValue = value;
|
|
}
|
|
else
|
|
{
|
|
var numAdditionalValues = length - 1;
|
|
ArrayHelpers.AppendWithCapacity(ref additionalValues, ref numAdditionalValues, value, capacityIncrement: capacityIncrement);
|
|
}
|
|
|
|
var index = length;
|
|
++length;
|
|
return index;
|
|
}
|
|
|
|
public void AssignWithCapacity(InlinedArray<TValue> values)
|
|
{
|
|
if (Capacity < values.length && values.length > 1)
|
|
additionalValues = new TValue[values.length - 1];
|
|
|
|
length = values.length;
|
|
if (length > 0)
|
|
firstValue = values.firstValue;
|
|
if (length > 1)
|
|
Array.Copy(values.additionalValues, additionalValues, length - 1);
|
|
}
|
|
|
|
public void Append(IEnumerable<TValue> values)
|
|
{
|
|
foreach (var value in values)
|
|
Append(value);
|
|
}
|
|
|
|
public void Remove(TValue value)
|
|
{
|
|
if (length < 1)
|
|
return;
|
|
|
|
if (EqualityComparer<TValue>.Default.Equals(firstValue, value))
|
|
{
|
|
RemoveAt(0);
|
|
}
|
|
else if (additionalValues != null)
|
|
{
|
|
for (var i = 0; i < length - 1; ++i)
|
|
{
|
|
if (EqualityComparer<TValue>.Default.Equals(additionalValues[i], value))
|
|
{
|
|
RemoveAt(i + 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveAtWithCapacity(int index)
|
|
{
|
|
if (index < 0 || index >= length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
if (index == 0)
|
|
{
|
|
if (length == 1)
|
|
{
|
|
firstValue = default;
|
|
}
|
|
else if (length == 2)
|
|
{
|
|
firstValue = additionalValues[0];
|
|
additionalValues[0] = default;
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(length > 2);
|
|
firstValue = additionalValues[0];
|
|
var numAdditional = length - 1;
|
|
ArrayHelpers.EraseAtWithCapacity(additionalValues, ref numAdditional, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var numAdditional = length - 1;
|
|
ArrayHelpers.EraseAtWithCapacity(additionalValues, ref numAdditional, index - 1);
|
|
}
|
|
|
|
--length;
|
|
}
|
|
|
|
public void RemoveAt(int index)
|
|
{
|
|
if (index < 0 || index >= length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
if (index == 0)
|
|
{
|
|
if (additionalValues != null)
|
|
{
|
|
firstValue = additionalValues[0];
|
|
if (additionalValues.Length == 1)
|
|
additionalValues = null;
|
|
else
|
|
{
|
|
Array.Copy(additionalValues, 1, additionalValues, 0, additionalValues.Length - 1);
|
|
Array.Resize(ref additionalValues, additionalValues.Length - 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
firstValue = default;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(additionalValues != null);
|
|
|
|
var numAdditionalValues = length - 1;
|
|
if (numAdditionalValues == 1)
|
|
{
|
|
// Remove only entry in array.
|
|
additionalValues = null;
|
|
}
|
|
else if (index == length - 1)
|
|
{
|
|
// Remove entry at end.
|
|
Array.Resize(ref additionalValues, numAdditionalValues - 1);
|
|
}
|
|
else
|
|
{
|
|
// Remove entry at beginning or in middle by pasting together
|
|
// into a new array.
|
|
var newAdditionalValues = new TValue[numAdditionalValues - 1];
|
|
if (index >= 2)
|
|
{
|
|
// Copy elements before entry.
|
|
Array.Copy(additionalValues, 0, newAdditionalValues, 0, index - 1);
|
|
}
|
|
|
|
// Copy elements after entry. We already know that we're not removing
|
|
// the last entry so there have to be entries.
|
|
Array.Copy(additionalValues, index + 1 - 1, newAdditionalValues, index - 1,
|
|
length - index - 1);
|
|
|
|
additionalValues = newAdditionalValues;
|
|
}
|
|
}
|
|
|
|
--length;
|
|
}
|
|
|
|
public void RemoveAtByMovingTailWithCapacity(int index)
|
|
{
|
|
if (index < 0 || index >= length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
var numAdditionalValues = length - 1;
|
|
if (index == 0)
|
|
{
|
|
if (length > 1)
|
|
{
|
|
firstValue = additionalValues[numAdditionalValues - 1];
|
|
additionalValues[numAdditionalValues - 1] = default;
|
|
}
|
|
else
|
|
{
|
|
firstValue = default;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(additionalValues != null);
|
|
|
|
ArrayHelpers.EraseAtByMovingTail(additionalValues, ref numAdditionalValues, index - 1);
|
|
}
|
|
|
|
--length;
|
|
}
|
|
|
|
public bool RemoveByMovingTailWithCapacity(TValue value)
|
|
{
|
|
var index = IndexOf(value);
|
|
if (index == -1)
|
|
return false;
|
|
|
|
RemoveAtByMovingTailWithCapacity(index);
|
|
return true;
|
|
}
|
|
|
|
public bool Contains(TValue value, IEqualityComparer<TValue> comparer)
|
|
{
|
|
for (var n = 0; n < length; ++n)
|
|
if (comparer.Equals(this[n], value))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public void Merge(InlinedArray<TValue> other)
|
|
{
|
|
var comparer = EqualityComparer<TValue>.Default;
|
|
for (var i = 0; i < other.length; ++i)
|
|
{
|
|
var value = other[i];
|
|
if (Contains(value, comparer))
|
|
continue;
|
|
|
|
////FIXME: this is ugly as it repeatedly copies
|
|
Append(value);
|
|
}
|
|
}
|
|
|
|
public IEnumerator<TValue> GetEnumerator()
|
|
{
|
|
return new Enumerator { array = this, index = -1 };
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
private struct Enumerator : IEnumerator<TValue>
|
|
{
|
|
public InlinedArray<TValue> array;
|
|
public int index;
|
|
|
|
public bool MoveNext()
|
|
{
|
|
if (index >= array.length)
|
|
return false;
|
|
++index;
|
|
return index < array.length;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
index = -1;
|
|
}
|
|
|
|
public TValue Current => array[index];
|
|
object IEnumerator.Current => Current;
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static class InputArrayExtensions
|
|
{
|
|
public static int IndexOfReference<TValue>(this InlinedArray<TValue> array, TValue value)
|
|
where TValue : class
|
|
{
|
|
for (var i = 0; i < array.length; ++i)
|
|
if (ReferenceEquals(array[i], value))
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
public static bool Contains<TValue>(this InlinedArray<TValue> array, TValue value)
|
|
{
|
|
for (var i = 0; i < array.length; ++i)
|
|
if (array[i].Equals(value))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public static bool ContainsReference<TValue>(this InlinedArray<TValue> array, TValue value)
|
|
where TValue : class
|
|
{
|
|
return IndexOfReference(array, value) != -1;
|
|
}
|
|
}
|
|
}
|