test
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user