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
{
///
/// Helper to avoid array allocations if there's only a single value in the array.
///
///
/// 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.
///
/// Element type for the array.
internal struct InlinedArray : IEnumerable
{
// 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 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 Clone()
{
return new InlinedArray
{
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(Func 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.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 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 values)
{
foreach (var value in values)
Append(value);
}
public void Remove(TValue value)
{
if (length < 1)
return;
if (EqualityComparer.Default.Equals(firstValue, value))
{
RemoveAt(0);
}
else if (additionalValues != null)
{
for (var i = 0; i < length - 1; ++i)
{
if (EqualityComparer.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 comparer)
{
for (var n = 0; n < length; ++n)
if (comparer.Equals(this[n], value))
return true;
return false;
}
public void Merge(InlinedArray other)
{
var comparer = EqualityComparer.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 GetEnumerator()
{
return new Enumerator { array = this, index = -1 };
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private struct Enumerator : IEnumerator
{
public InlinedArray 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(this InlinedArray 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(this InlinedArray array, TValue value)
{
for (var i = 0; i < array.length; ++i)
if (array[i].Equals(value))
return true;
return false;
}
public static bool ContainsReference(this InlinedArray array, TValue value)
where TValue : class
{
return IndexOfReference(array, value) != -1;
}
}
}