UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,813 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of utility functions for working with arrays.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The goal of this collection is to make it easy to use arrays directly rather than resorting to
|
||||
/// <see cref="List{T}"/>.
|
||||
/// </remarks>
|
||||
internal static class ArrayHelpers
|
||||
{
|
||||
public static int LengthSafe<TValue>(this TValue[] array)
|
||||
{
|
||||
if (array == null)
|
||||
return 0;
|
||||
return array.Length;
|
||||
}
|
||||
|
||||
public static void Clear<TValue>(this TValue[] array)
|
||||
{
|
||||
if (array == null)
|
||||
return;
|
||||
|
||||
Array.Clear(array, 0, array.Length);
|
||||
}
|
||||
|
||||
public static void Clear<TValue>(this TValue[] array, int count)
|
||||
{
|
||||
if (array == null)
|
||||
return;
|
||||
Array.Clear(array, 0, count);
|
||||
}
|
||||
|
||||
public static void Clear<TValue>(this TValue[] array, ref int count)
|
||||
{
|
||||
if (array == null)
|
||||
return;
|
||||
|
||||
Array.Clear(array, 0, count);
|
||||
count = 0;
|
||||
}
|
||||
|
||||
public static void EnsureCapacity<TValue>(ref TValue[] array, int count, int capacity, int capacityIncrement = 10)
|
||||
{
|
||||
if (capacity == 0)
|
||||
return;
|
||||
|
||||
if (array == null)
|
||||
{
|
||||
array = new TValue[Math.Max(capacity, capacityIncrement)];
|
||||
return;
|
||||
}
|
||||
|
||||
var currentCapacity = array.Length - count;
|
||||
if (currentCapacity >= capacity)
|
||||
return;
|
||||
|
||||
DuplicateWithCapacity(ref array, count, capacity, capacityIncrement);
|
||||
}
|
||||
|
||||
public static void DuplicateWithCapacity<TValue>(ref TValue[] array, int count, int capacity, int capacityIncrement = 10)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
array = new TValue[Math.Max(capacity, capacityIncrement)];
|
||||
return;
|
||||
}
|
||||
|
||||
var newSize = count + Math.Max(capacity, capacityIncrement);
|
||||
var newArray = new TValue[newSize];
|
||||
Array.Copy(array, newArray, count);
|
||||
array = newArray;
|
||||
}
|
||||
|
||||
public static bool Contains<TValue>(TValue[] array, TValue value)
|
||||
{
|
||||
if (array == null)
|
||||
return false;
|
||||
|
||||
var comparer = EqualityComparer<TValue>.Default;
|
||||
for (var i = 0; i < array.Length; ++i)
|
||||
if (comparer.Equals(array[i], value))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ContainsReference<TValue>(this TValue[] array, TValue value)
|
||||
where TValue : class
|
||||
{
|
||||
if (array == null)
|
||||
return false;
|
||||
|
||||
return ContainsReference(array, array.Length, value);
|
||||
}
|
||||
|
||||
public static bool ContainsReference<TFirst, TSecond>(this TFirst[] array, int count, TSecond value)
|
||||
where TSecond : class
|
||||
where TFirst : TSecond
|
||||
{
|
||||
return IndexOfReference(array, value, count) != -1;
|
||||
}
|
||||
|
||||
public static bool ContainsReference<TFirst, TSecond>(this TFirst[] array, int startIndex, int count, TSecond value)
|
||||
where TSecond : class
|
||||
where TFirst : TSecond
|
||||
{
|
||||
return IndexOfReference(array, value, startIndex, count) != -1;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Keep this for future implementation")]
|
||||
public static bool HaveDuplicateReferences<TFirst>(this TFirst[] first, int index, int count)
|
||||
{
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var element = first[i];
|
||||
for (var n = i + 1; n < count - i; ++n)
|
||||
{
|
||||
if (ReferenceEquals(element, first[n]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HaveEqualElements<TValue>(TValue[] first, TValue[] second, int count = int.MaxValue)
|
||||
{
|
||||
if (first == null || second == null)
|
||||
return second == first;
|
||||
|
||||
var lengthFirst = Math.Min(count, first.Length);
|
||||
var lengthSecond = Math.Min(count, second.Length);
|
||||
|
||||
if (lengthFirst != lengthSecond)
|
||||
return false;
|
||||
|
||||
var comparer = EqualityComparer<TValue>.Default;
|
||||
for (var i = 0; i < lengthFirst; ++i)
|
||||
if (!comparer.Equals(first[i], second[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////REVIEW: remove this to get rid of default equality comparer?
|
||||
public static int IndexOf<TValue>(TValue[] array, TValue value, int startIndex = 0, int count = -1)
|
||||
{
|
||||
if (array == null)
|
||||
return -1;
|
||||
|
||||
if (count < 0)
|
||||
count = array.Length - startIndex;
|
||||
var comparer = EqualityComparer<TValue>.Default;
|
||||
for (var i = startIndex; i < startIndex + count; ++i)
|
||||
if (comparer.Equals(array[i], value))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int IndexOf<TValue>(this TValue[] array, Predicate<TValue> predicate)
|
||||
{
|
||||
if (array == null)
|
||||
return -1;
|
||||
|
||||
var length = array.Length;
|
||||
for (var i = 0; i < length; ++i)
|
||||
if (predicate(array[i]))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int IndexOf<TValue>(this TValue[] array, Predicate<TValue> predicate, int startIndex = 0, int count = -1)
|
||||
{
|
||||
if (array == null)
|
||||
return -1;
|
||||
|
||||
var end = startIndex + (count < 0 ? array.Length - startIndex : count);
|
||||
for (var i = startIndex; i < end; ++i)
|
||||
{
|
||||
if (predicate(array[i]))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int IndexOfReference<TFirst, TSecond>(this TFirst[] array, TSecond value, int count = -1)
|
||||
where TSecond : class
|
||||
where TFirst : TSecond
|
||||
{
|
||||
return IndexOfReference(array, value, 0, count);
|
||||
}
|
||||
|
||||
public static int IndexOfReference<TFirst, TSecond>(this TFirst[] array, TSecond value, int startIndex, int count)
|
||||
where TSecond : class
|
||||
where TFirst : TSecond
|
||||
{
|
||||
if (array == null)
|
||||
return -1;
|
||||
|
||||
if (count < 0)
|
||||
count = array.Length - startIndex;
|
||||
for (var i = startIndex; i < startIndex + count; ++i)
|
||||
if (ReferenceEquals(array[i], value))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int IndexOfValue<TValue>(this TValue[] array, TValue value, int startIndex = 0, int count = -1)
|
||||
where TValue : struct, IEquatable<TValue>
|
||||
{
|
||||
if (array == null)
|
||||
return -1;
|
||||
|
||||
if (count < 0)
|
||||
count = array.Length - startIndex;
|
||||
for (var i = startIndex; i < startIndex + count; ++i)
|
||||
if (value.Equals(array[i]))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static unsafe void Resize<TValue>(ref NativeArray<TValue> array, int newSize, Allocator allocator)
|
||||
where TValue : struct
|
||||
{
|
||||
var oldSize = array.Length;
|
||||
if (oldSize == newSize)
|
||||
return;
|
||||
|
||||
if (newSize == 0)
|
||||
{
|
||||
if (array.IsCreated)
|
||||
array.Dispose();
|
||||
array = new NativeArray<TValue>();
|
||||
return;
|
||||
}
|
||||
|
||||
var newArray = new NativeArray<TValue>(newSize, allocator);
|
||||
if (oldSize != 0)
|
||||
{
|
||||
// Copy contents from old array.
|
||||
UnsafeUtility.MemCpy(newArray.GetUnsafePtr(), array.GetUnsafeReadOnlyPtr(),
|
||||
UnsafeUtility.SizeOf<TValue>() * (newSize < oldSize ? newSize : oldSize));
|
||||
array.Dispose();
|
||||
}
|
||||
array = newArray;
|
||||
}
|
||||
|
||||
public static int Append<TValue>(ref TValue[] array, TValue value)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
array = new TValue[1];
|
||||
array[0] = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var length = array.Length;
|
||||
Array.Resize(ref array, length + 1);
|
||||
array[length] = value;
|
||||
return length;
|
||||
}
|
||||
|
||||
public static int Append<TValue>(ref TValue[] array, IEnumerable<TValue> values)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
array = values.ToArray();
|
||||
return 0;
|
||||
}
|
||||
|
||||
var oldLength = array.Length;
|
||||
var valueCount = values.Count();
|
||||
|
||||
Array.Resize(ref array, oldLength + valueCount);
|
||||
|
||||
var index = oldLength;
|
||||
foreach (var value in values)
|
||||
array[index++] = value;
|
||||
|
||||
return oldLength;
|
||||
}
|
||||
|
||||
// Append to an array that is considered immutable. This allows using 'values' as is
|
||||
// if 'array' is null.
|
||||
// Returns the index of the first newly added element in the resulting array.
|
||||
public static int AppendToImmutable<TValue>(ref TValue[] array, TValue[] values)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
array = values;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (values != null && values.Length > 0)
|
||||
{
|
||||
var oldCount = array.Length;
|
||||
var valueCount = values.Length;
|
||||
Array.Resize(ref array, oldCount + valueCount);
|
||||
Array.Copy(values, 0, array, oldCount, valueCount);
|
||||
return oldCount;
|
||||
}
|
||||
|
||||
return array.Length;
|
||||
}
|
||||
|
||||
public static int AppendWithCapacity<TValue>(ref TValue[] array, ref int count, TValue value, int capacityIncrement = 10)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
array = new TValue[capacityIncrement];
|
||||
array[0] = value;
|
||||
++count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var capacity = array.Length;
|
||||
if (capacity == count)
|
||||
{
|
||||
capacity += capacityIncrement;
|
||||
Array.Resize(ref array, capacity);
|
||||
}
|
||||
|
||||
var index = count;
|
||||
array[index] = value;
|
||||
++count;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int AppendListWithCapacity<TValue, TValues>(ref TValue[] array, ref int length, TValues values, int capacityIncrement = 10)
|
||||
where TValues : IReadOnlyList<TValue>
|
||||
{
|
||||
var numToAdd = values.Count;
|
||||
if (array == null)
|
||||
{
|
||||
var size = Math.Max(numToAdd, capacityIncrement);
|
||||
array = new TValue[size];
|
||||
for (var i = 0; i < numToAdd; ++i)
|
||||
array[i] = values[i];
|
||||
length += numToAdd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var capacity = array.Length;
|
||||
if (capacity < length + numToAdd)
|
||||
{
|
||||
capacity += Math.Max(length + numToAdd, capacityIncrement);
|
||||
Array.Resize(ref array, capacity);
|
||||
}
|
||||
|
||||
var index = length;
|
||||
for (var i = 0; i < numToAdd; ++i)
|
||||
array[index + i] = values[i];
|
||||
length += numToAdd;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int AppendWithCapacity<TValue>(ref NativeArray<TValue> array, ref int count, TValue value,
|
||||
int capacityIncrement = 10, Allocator allocator = Allocator.Persistent)
|
||||
where TValue : struct
|
||||
{
|
||||
var capacity = array.Length;
|
||||
if (capacity == count)
|
||||
GrowBy(ref array, capacityIncrement > 1 ? capacityIncrement : 1, allocator);
|
||||
|
||||
var index = count;
|
||||
array[index] = value;
|
||||
++count;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static void InsertAt<TValue>(ref TValue[] array, int index, TValue value)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
////REVIEW: allow growing array to specific size by inserting at arbitrary index?
|
||||
if (index != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
array = new TValue[1];
|
||||
array[0] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reallocate.
|
||||
var oldLength = array.Length;
|
||||
Array.Resize(ref array, oldLength + 1);
|
||||
|
||||
// Make room for element.
|
||||
if (index != oldLength)
|
||||
Array.Copy(array, index, array, index + 1, oldLength - index);
|
||||
|
||||
array[index] = value;
|
||||
}
|
||||
|
||||
public static void InsertAtWithCapacity<TValue>(ref TValue[] array, ref int count, int index, TValue value, int capacityIncrement = 10)
|
||||
{
|
||||
EnsureCapacity(ref array, count, count + 1, capacityIncrement);
|
||||
|
||||
if (index != count)
|
||||
Array.Copy(array, index, array, index + 1, count - index);
|
||||
|
||||
array[index] = value;
|
||||
++count;
|
||||
}
|
||||
|
||||
public static void PutAtIfNotSet<TValue>(ref TValue[] array, int index, Func<TValue> valueFn)
|
||||
{
|
||||
if (array.LengthSafe() < index + 1)
|
||||
Array.Resize(ref array, index + 1);
|
||||
|
||||
if (EqualityComparer<TValue>.Default.Equals(array[index], default(TValue)))
|
||||
array[index] = valueFn();
|
||||
}
|
||||
|
||||
// Adds 'count' entries to the array. Returns first index of newly added entries.
|
||||
public static int GrowBy<TValue>(ref TValue[] array, int count)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
array = new TValue[count];
|
||||
return 0;
|
||||
}
|
||||
|
||||
var oldLength = array.Length;
|
||||
Array.Resize(ref array, oldLength + count);
|
||||
return oldLength;
|
||||
}
|
||||
|
||||
public static unsafe int GrowBy<TValue>(ref NativeArray<TValue> array, int count, Allocator allocator = Allocator.Persistent)
|
||||
where TValue : struct
|
||||
{
|
||||
var length = array.Length;
|
||||
if (length == 0)
|
||||
{
|
||||
array = new NativeArray<TValue>(count, allocator);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var newArray = new NativeArray<TValue>(length + count, allocator);
|
||||
// CopyFrom() expects length to match. Copy manually.
|
||||
UnsafeUtility.MemCpy(newArray.GetUnsafePtr(), array.GetUnsafeReadOnlyPtr(), (long)length * UnsafeUtility.SizeOf<TValue>());
|
||||
array.Dispose();
|
||||
array = newArray;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public static int GrowWithCapacity<TValue>(ref TValue[] array, ref int count, int growBy, int capacityIncrement = 10)
|
||||
{
|
||||
var length = array != null ? array.Length : 0;
|
||||
if (length < count + growBy)
|
||||
{
|
||||
if (capacityIncrement < growBy)
|
||||
capacityIncrement = growBy;
|
||||
GrowBy(ref array, capacityIncrement);
|
||||
}
|
||||
|
||||
var offset = count;
|
||||
count += growBy;
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static int GrowWithCapacity<TValue>(ref NativeArray<TValue> array, ref int count, int growBy,
|
||||
int capacityIncrement = 10, Allocator allocator = Allocator.Persistent)
|
||||
where TValue : struct
|
||||
{
|
||||
var length = array.Length;
|
||||
if (length < count + growBy)
|
||||
{
|
||||
if (capacityIncrement < growBy)
|
||||
capacityIncrement = growBy;
|
||||
GrowBy(ref array, capacityIncrement, allocator);
|
||||
}
|
||||
|
||||
var offset = count;
|
||||
count += growBy;
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static TValue[] Join<TValue>(TValue value, params TValue[] values)
|
||||
{
|
||||
// Determine length.
|
||||
var length = 0;
|
||||
if (value != null)
|
||||
++length;
|
||||
if (values != null)
|
||||
length += values.Length;
|
||||
|
||||
if (length == 0)
|
||||
return null;
|
||||
|
||||
var array = new TValue[length];
|
||||
|
||||
// Populate.
|
||||
var index = 0;
|
||||
if (value != null)
|
||||
array[index++] = value;
|
||||
|
||||
if (values != null)
|
||||
Array.Copy(values, 0, array, index, values.Length);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static TValue[] Merge<TValue>(TValue[] first, TValue[] second)
|
||||
where TValue : IEquatable<TValue>
|
||||
{
|
||||
if (first == null)
|
||||
return second;
|
||||
if (second == null)
|
||||
return first;
|
||||
|
||||
var merged = new List<TValue>();
|
||||
merged.AddRange(first);
|
||||
|
||||
for (var i = 0; i < second.Length; ++i)
|
||||
{
|
||||
var secondValue = second[i];
|
||||
if (!merged.Exists(x => x.Equals(secondValue)))
|
||||
{
|
||||
merged.Add(secondValue);
|
||||
}
|
||||
}
|
||||
|
||||
return merged.ToArray();
|
||||
}
|
||||
|
||||
public static TValue[] Merge<TValue>(TValue[] first, TValue[] second, IEqualityComparer<TValue> comparer)
|
||||
{
|
||||
if (first == null)
|
||||
return second;
|
||||
if (second == null)
|
||||
return null;
|
||||
|
||||
var merged = new List<TValue>();
|
||||
merged.AddRange(first);
|
||||
|
||||
for (var i = 0; i < second.Length; ++i)
|
||||
{
|
||||
var secondValue = second[i];
|
||||
if (!merged.Exists(x => comparer.Equals(secondValue)))
|
||||
{
|
||||
merged.Add(secondValue);
|
||||
}
|
||||
}
|
||||
|
||||
return merged.ToArray();
|
||||
}
|
||||
|
||||
public static void EraseAt<TValue>(ref TValue[] array, int index)
|
||||
{
|
||||
Debug.Assert(array != null);
|
||||
Debug.Assert(index >= 0 && index < array.Length);
|
||||
|
||||
var length = array.Length;
|
||||
if (index == 0 && length == 1)
|
||||
{
|
||||
array = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < length - 1)
|
||||
Array.Copy(array, index + 1, array, index, length - index - 1);
|
||||
|
||||
Array.Resize(ref array, length - 1);
|
||||
}
|
||||
|
||||
public static void EraseAtWithCapacity<TValue>(this TValue[] array, ref int count, int index)
|
||||
{
|
||||
Debug.Assert(array != null);
|
||||
Debug.Assert(count <= array.Length);
|
||||
Debug.Assert(index >= 0 && index < count);
|
||||
|
||||
// If we're erasing from the beginning or somewhere in the middle, move
|
||||
// the array contents down from after the index.
|
||||
if (index < count - 1)
|
||||
{
|
||||
Array.Copy(array, index + 1, array, index, count - index - 1);
|
||||
}
|
||||
|
||||
array[count - 1] = default; // Tail has been moved down by one.
|
||||
--count;
|
||||
}
|
||||
|
||||
public static unsafe void EraseAtWithCapacity<TValue>(NativeArray<TValue> array, ref int count, int index)
|
||||
where TValue : struct
|
||||
{
|
||||
Debug.Assert(array.IsCreated);
|
||||
Debug.Assert(count <= array.Length);
|
||||
Debug.Assert(index >= 0 && index < count);
|
||||
|
||||
// If we're erasing from the beginning or somewhere in the middle, move
|
||||
// the array contents down from after the index.
|
||||
if (index < count - 1)
|
||||
{
|
||||
var elementSize = UnsafeUtility.SizeOf<TValue>();
|
||||
var arrayPtr = (byte*)array.GetUnsafePtr();
|
||||
|
||||
UnsafeUtility.MemCpy(arrayPtr + elementSize * index, arrayPtr + elementSize * (index + 1),
|
||||
(count - index - 1) * elementSize);
|
||||
}
|
||||
|
||||
--count;
|
||||
}
|
||||
|
||||
public static bool Erase<TValue>(ref TValue[] array, TValue value)
|
||||
{
|
||||
var index = IndexOf(array, value);
|
||||
if (index != -1)
|
||||
{
|
||||
EraseAt(ref array, index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erase an element from the array by moving the tail element into its place.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to modify. May be not <c>null</c>.</param>
|
||||
/// <param name="count">Current number of elements inside of array. May be less than <c>array.Length</c>.</param>
|
||||
/// <param name="index">Index of element to remove. Tail element will get moved into its place.</param>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <remarks>
|
||||
/// This method does not re-allocate the array. Instead <paramref name="count"/> is used
|
||||
/// to keep track of how many elements there actually are in the array.
|
||||
/// </remarks>
|
||||
public static void EraseAtByMovingTail<TValue>(TValue[] array, ref int count, int index)
|
||||
{
|
||||
Debug.Assert(array != null);
|
||||
Debug.Assert(index >= 0 && index < array.Length);
|
||||
Debug.Assert(count >= 0 && count <= array.Length);
|
||||
Debug.Assert(index < count);
|
||||
|
||||
// Move tail, if necessary.
|
||||
if (index != count - 1)
|
||||
array[index] = array[count - 1];
|
||||
|
||||
// Destroy current tail.
|
||||
if (count >= 1)
|
||||
array[count - 1] = default;
|
||||
--count;
|
||||
}
|
||||
|
||||
public static TValue[] Copy<TValue>(TValue[] array)
|
||||
{
|
||||
if (array == null)
|
||||
return null;
|
||||
|
||||
var length = array.Length;
|
||||
var result = new TValue[length];
|
||||
Array.Copy(array, result, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TValue[] Clone<TValue>(TValue[] array)
|
||||
where TValue : ICloneable
|
||||
{
|
||||
if (array == null)
|
||||
return null;
|
||||
|
||||
var count = array.Length;
|
||||
var result = new TValue[count];
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
result[i] = (TValue)array[i].Clone();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TNew[] Select<TOld, TNew>(TOld[] array, Func<TOld, TNew> converter)
|
||||
{
|
||||
if (array == null)
|
||||
return null;
|
||||
|
||||
var length = array.Length;
|
||||
var result = new TNew[length];
|
||||
|
||||
for (var i = 0; i < length; ++i)
|
||||
result[i] = converter(array[i]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void Swap<TValue>(ref TValue first, ref TValue second)
|
||||
{
|
||||
var temp = first;
|
||||
first = second;
|
||||
second = temp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a slice in the array to a different place without allocating a temporary array.
|
||||
/// </summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="sourceIndex"></param>
|
||||
/// <param name="destinationIndex"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <remarks>
|
||||
/// The slice is moved by repeatedly swapping slices until all the slices are where they
|
||||
/// are supposed to go. This is not super efficient but avoids having to allocate a temporary
|
||||
/// array on the heap.
|
||||
/// </remarks>
|
||||
public static void MoveSlice<TValue>(TValue[] array, int sourceIndex, int destinationIndex, int count)
|
||||
{
|
||||
if (count <= 0 || sourceIndex == destinationIndex)
|
||||
return;
|
||||
|
||||
// Determine the number of elements in the window.
|
||||
int elementCount;
|
||||
if (destinationIndex > sourceIndex)
|
||||
elementCount = destinationIndex + count - sourceIndex;
|
||||
else
|
||||
elementCount = sourceIndex + count - destinationIndex;
|
||||
|
||||
// If the source and target slice are right next to each other, just go
|
||||
// and swap out the elements in both slices.
|
||||
if (elementCount == count * 2)
|
||||
{
|
||||
for (var i = 0; i < count; ++i)
|
||||
Swap(ref array[sourceIndex + i], ref array[destinationIndex + i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There's elements in-between the two slices.
|
||||
//
|
||||
// The easiest way to picture this operation is as a rotation of the elements within
|
||||
// the window given by sourceIndex, destination, and count. Within that window, we are
|
||||
// simply treating it as a wrap-around buffer and then sliding the elements clockwise
|
||||
// or counter-clockwise (depending on whether we move up or down, respectively) through
|
||||
// the window.
|
||||
//
|
||||
// Unfortunately, we can't just memcopy the slices within that window as we have to
|
||||
// have a temporary copy in place in order to preserve element values. So instead, we
|
||||
// go and swap elements one by one, something that doesn't require anything other than
|
||||
// a single value temporary copy.
|
||||
|
||||
// Determine the number of swaps we need to achieve the desired order. Swaps
|
||||
// operate in pairs so it's one less than the number of elements in the range.
|
||||
var swapCount = elementCount - 1;
|
||||
|
||||
// We simply take sourceIndex as fixed and do all swaps from there until all
|
||||
// the elements in the window are in the right order. Each swap will put one
|
||||
// element in its final place.
|
||||
var dst = destinationIndex;
|
||||
for (var i = 0; i < swapCount; ++i)
|
||||
{
|
||||
// Swap source into its destination place. This puts the current sourceIndex
|
||||
// element in its final place.
|
||||
Swap(ref array[dst], ref array[sourceIndex]);
|
||||
|
||||
// Find out where the element that we now swapped into sourceIndex should
|
||||
// actually go.
|
||||
if (destinationIndex > sourceIndex)
|
||||
{
|
||||
// Rotating clockwise.
|
||||
dst -= count;
|
||||
if (dst < sourceIndex)
|
||||
dst = destinationIndex + count - Math.Abs(sourceIndex - dst); // Wrap around.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rotating counter-clockwise.
|
||||
dst += count;
|
||||
if (dst >= sourceIndex + count)
|
||||
dst = destinationIndex + (dst - (sourceIndex + count)); // Wrap around.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EraseSliceWithCapacity<TValue>(ref TValue[] array, ref int length, int index, int count)
|
||||
{
|
||||
// Move elements down.
|
||||
if (count < length)
|
||||
Array.Copy(array, index + count, array, index, length - index - count);
|
||||
|
||||
// Erase now vacant slots.
|
||||
for (var i = 0; i < count; ++i)
|
||||
array[length - i - 1] = default;
|
||||
|
||||
length -= count;
|
||||
}
|
||||
|
||||
public static void SwapElements<TValue>(this TValue[] array, int index1, int index2)
|
||||
{
|
||||
MemoryHelpers.Swap(ref array[index1], ref array[index2]);
|
||||
}
|
||||
|
||||
public static void SwapElements<TValue>(this NativeArray<TValue> array, int index1, int index2)
|
||||
where TValue : struct
|
||||
{
|
||||
var temp = array[index1];
|
||||
array[index1] = array[index2];
|
||||
array[index2] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 888687b767334731951f3f18ffef0b75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
////REVIEW: this seems like it should be #if UNITY_EDITOR
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class CSharpCodeHelpers
|
||||
{
|
||||
public static bool IsProperIdentifier(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return false;
|
||||
|
||||
if (char.IsDigit(name[0]))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < name.Length; ++i)
|
||||
{
|
||||
var ch = name[i];
|
||||
if (!char.IsLetterOrDigit(ch) && ch != '_')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsEmptyOrProperIdentifier(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return true;
|
||||
|
||||
return IsProperIdentifier(name);
|
||||
}
|
||||
|
||||
public static bool IsEmptyOrProperNamespaceName(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return true;
|
||||
|
||||
return name.Split('.').All(IsProperIdentifier);
|
||||
}
|
||||
|
||||
////TODO: this one should add the @escape automatically so no other code has to worry
|
||||
public static string MakeIdentifier(string name, string suffix = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
if (char.IsDigit(name[0]))
|
||||
name = "_" + name;
|
||||
|
||||
// See if we have invalid characters in the name.
|
||||
var nameHasInvalidCharacters = false;
|
||||
for (var i = 0; i < name.Length; ++i)
|
||||
{
|
||||
var ch = name[i];
|
||||
if (!char.IsLetterOrDigit(ch) && ch != '_')
|
||||
{
|
||||
nameHasInvalidCharacters = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If so, create a new string where we remove them.
|
||||
if (nameHasInvalidCharacters)
|
||||
{
|
||||
var buffer = new StringBuilder();
|
||||
for (var i = 0; i < name.Length; ++i)
|
||||
{
|
||||
var ch = name[i];
|
||||
if (char.IsLetterOrDigit(ch) || ch == '_')
|
||||
buffer.Append(ch);
|
||||
}
|
||||
|
||||
name = buffer.ToString();
|
||||
}
|
||||
|
||||
return name + suffix;
|
||||
}
|
||||
|
||||
public static string MakeTypeName(string name, string suffix = "")
|
||||
{
|
||||
var symbolName = MakeIdentifier(name, suffix);
|
||||
if (char.IsLower(symbolName[0]))
|
||||
symbolName = char.ToUpperInvariant(symbolName[0]) + symbolName.Substring(1);
|
||||
return symbolName;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static string MakeAutoGeneratedCodeHeader(string toolName, string toolVersion, string sourceFileName = null)
|
||||
{
|
||||
return
|
||||
"//------------------------------------------------------------------------------\n"
|
||||
+ "// <auto-generated>\n"
|
||||
+ $"// This code was auto-generated by {toolName}\n"
|
||||
+ $"// version {toolVersion}\n"
|
||||
+ (string.IsNullOrEmpty(sourceFileName) ? "" : $"// from {sourceFileName}\n")
|
||||
+ "//\n"
|
||||
+ "// Changes to this file may cause incorrect behavior and will be lost if\n"
|
||||
+ "// the code is regenerated.\n"
|
||||
+ "// </auto-generated>\n"
|
||||
+ "//------------------------------------------------------------------------------\n";
|
||||
}
|
||||
|
||||
public static string ToLiteral(this object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "null";
|
||||
|
||||
var type = value.GetType();
|
||||
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
if ((bool)value)
|
||||
return "true";
|
||||
return "false";
|
||||
}
|
||||
|
||||
if (type == typeof(char))
|
||||
return $"'\\u{(int)(char)value:X2}'";
|
||||
|
||||
if (type == typeof(float))
|
||||
return value + "f";
|
||||
|
||||
if (type == typeof(uint) || type == typeof(ulong))
|
||||
return value + "u";
|
||||
|
||||
if (type == typeof(long))
|
||||
return value + "l";
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
var enumValue = type.GetEnumName(value);
|
||||
if (!string.IsNullOrEmpty(enumValue))
|
||||
return $"{type.FullName.Replace("+", ".")}.{enumValue}";
|
||||
}
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
public static string GetInitializersForPublicPrimitiveTypeFields(this object instance)
|
||||
{
|
||||
var type = instance.GetType();
|
||||
var defaults = Activator.CreateInstance(type);
|
||||
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
|
||||
var fieldInits = string.Join(", ",
|
||||
fields.Where(f => (f.FieldType.IsPrimitive || f.FieldType.IsEnum) && !f.GetValue(instance).Equals(f.GetValue(defaults)))
|
||||
.Select(f => $"{f.Name} = {f.GetValue(instance).ToLiteral()}"));
|
||||
|
||||
if (string.IsNullOrEmpty(fieldInits))
|
||||
return "()";
|
||||
|
||||
return " { " + fieldInits + " }";
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c6a2891083312645a7be416176261c1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
// Keeps a copy of the callback list while executing so that the callback list can safely
|
||||
// be mutated from within callbacks.
|
||||
internal struct CallbackArray<TDelegate>
|
||||
where TDelegate : System.Delegate
|
||||
{
|
||||
private bool m_CannotMutateCallbacksArray;
|
||||
private InlinedArray<TDelegate> m_Callbacks;
|
||||
private InlinedArray<TDelegate> m_CallbacksToAdd;
|
||||
private InlinedArray<TDelegate> m_CallbacksToRemove;
|
||||
|
||||
public int length => m_Callbacks.length;
|
||||
|
||||
public TDelegate this[int index] => m_Callbacks[index];
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_Callbacks.Clear();
|
||||
m_CallbacksToAdd.Clear();
|
||||
m_CallbacksToRemove.Clear();
|
||||
}
|
||||
|
||||
public void AddCallback(TDelegate dlg)
|
||||
{
|
||||
if (m_CannotMutateCallbacksArray)
|
||||
{
|
||||
if (m_CallbacksToAdd.Contains(dlg))
|
||||
return;
|
||||
var removeIndex = m_CallbacksToRemove.IndexOf(dlg);
|
||||
if (removeIndex != -1)
|
||||
m_CallbacksToRemove.RemoveAtByMovingTailWithCapacity(removeIndex);
|
||||
m_CallbacksToAdd.AppendWithCapacity(dlg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_Callbacks.Contains(dlg))
|
||||
m_Callbacks.AppendWithCapacity(dlg, capacityIncrement: 4);
|
||||
}
|
||||
|
||||
public void RemoveCallback(TDelegate dlg)
|
||||
{
|
||||
if (m_CannotMutateCallbacksArray)
|
||||
{
|
||||
if (m_CallbacksToRemove.Contains(dlg))
|
||||
return;
|
||||
var addIndex = m_CallbacksToAdd.IndexOf(dlg);
|
||||
if (addIndex != -1)
|
||||
m_CallbacksToAdd.RemoveAtByMovingTailWithCapacity(addIndex);
|
||||
m_CallbacksToRemove.AppendWithCapacity(dlg);
|
||||
return;
|
||||
}
|
||||
|
||||
var index = m_Callbacks.IndexOf(dlg);
|
||||
if (index >= 0)
|
||||
m_Callbacks.RemoveAtWithCapacity(index);
|
||||
}
|
||||
|
||||
public void LockForChanges()
|
||||
{
|
||||
m_CannotMutateCallbacksArray = true;
|
||||
}
|
||||
|
||||
public void UnlockForChanges()
|
||||
{
|
||||
m_CannotMutateCallbacksArray = false;
|
||||
|
||||
// Process mutations that have happened while we were executing callbacks.
|
||||
for (var i = 0; i < m_CallbacksToRemove.length; ++i)
|
||||
RemoveCallback(m_CallbacksToRemove[i]);
|
||||
for (var i = 0; i < m_CallbacksToAdd.length; ++i)
|
||||
AddCallback(m_CallbacksToAdd[i]);
|
||||
|
||||
m_CallbacksToAdd.Clear();
|
||||
m_CallbacksToRemove.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2349376ef4c6487db94a6e15873ee6a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two <see cref="Vector2"/> by magnitude.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// </code>
|
||||
/// public class CompositeWithVector2Part : InputBindingComposite<Vector2>
|
||||
/// {
|
||||
/// [InputControl(layout = "Vector2")]
|
||||
/// public int part;
|
||||
///
|
||||
/// public override Vector2 ReadValue(ref InputBindingCompositeContext context)
|
||||
/// {
|
||||
/// // Return the Vector3 with the greatest magnitude.
|
||||
/// return context.ReadValue<Vector2, Vector2MagnitudeComparer>(part);
|
||||
/// }
|
||||
/// }
|
||||
/// </example>
|
||||
public struct Vector2MagnitudeComparer : IComparer<Vector2>
|
||||
{
|
||||
public int Compare(Vector2 x, Vector2 y)
|
||||
{
|
||||
var lenx = x.sqrMagnitude;
|
||||
var leny = y.sqrMagnitude;
|
||||
|
||||
if (lenx < leny)
|
||||
return -1;
|
||||
if (lenx > leny)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two <see cref="Vector3"/> by magnitude.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// </code>
|
||||
/// public class CompositeWithVector3Part : InputBindingComposite<Vector3>
|
||||
/// {
|
||||
/// [InputControl(layout = "Vector3")]
|
||||
/// public int part;
|
||||
///
|
||||
/// public override Vector3 ReadValue(ref InputBindingCompositeContext context)
|
||||
/// {
|
||||
/// // Return the Vector3 with the greatest magnitude.
|
||||
/// return context.ReadValue<Vector3, Vector2MagnitudeComparer>(part);
|
||||
/// }
|
||||
/// }
|
||||
/// </example>
|
||||
public struct Vector3MagnitudeComparer : IComparer<Vector3>
|
||||
{
|
||||
public int Compare(Vector3 x, Vector3 y)
|
||||
{
|
||||
var lenx = x.sqrMagnitude;
|
||||
var leny = y.sqrMagnitude;
|
||||
|
||||
if (lenx < leny)
|
||||
return -1;
|
||||
if (lenx > leny)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e687839a3c5f7a4ea700f2967051b3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class DelegateHelpers
|
||||
{
|
||||
// InvokeCallbacksSafe protects both against the callback getting removed while being called
|
||||
// and against exceptions being thrown by the callback.
|
||||
|
||||
public static void InvokeCallbacksSafe(ref CallbackArray<Action> callbacks, ProfilerMarker marker, string callbackName, object context = null)
|
||||
{
|
||||
if (callbacks.length == 0)
|
||||
return;
|
||||
marker.Begin();
|
||||
callbacks.LockForChanges();
|
||||
for (var i = 0; i < callbacks.length; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
callbacks[i]();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
if (context != null)
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks of '{context}'");
|
||||
else
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks");
|
||||
}
|
||||
}
|
||||
callbacks.UnlockForChanges();
|
||||
marker.End();
|
||||
}
|
||||
|
||||
public static void InvokeCallbacksSafe<TValue>(ref CallbackArray<Action<TValue>> callbacks, TValue argument, string callbackName, object context = null)
|
||||
{
|
||||
if (callbacks.length == 0)
|
||||
return;
|
||||
Profiling.Profiler.BeginSample(callbackName);
|
||||
callbacks.LockForChanges();
|
||||
for (var i = 0; i < callbacks.length; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
callbacks[i](argument);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
if (context != null)
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks of '{context}'");
|
||||
else
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks");
|
||||
}
|
||||
}
|
||||
callbacks.UnlockForChanges();
|
||||
Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
public static void InvokeCallbacksSafe<TValue1, TValue2>(ref CallbackArray<Action<TValue1, TValue2>> callbacks, TValue1 argument1, TValue2 argument2, ProfilerMarker marker, string callbackName, object context = null)
|
||||
{
|
||||
if (callbacks.length == 0)
|
||||
return;
|
||||
marker.Begin();
|
||||
callbacks.LockForChanges();
|
||||
for (var i = 0; i < callbacks.length; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
callbacks[i](argument1, argument2);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
if (context != null)
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks of '{context}'");
|
||||
else
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks");
|
||||
}
|
||||
}
|
||||
callbacks.UnlockForChanges();
|
||||
marker.End();
|
||||
}
|
||||
|
||||
public static bool InvokeCallbacksSafe_AnyCallbackReturnsTrue<TValue1, TValue2>(ref CallbackArray<Func<TValue1, TValue2, bool>> callbacks,
|
||||
TValue1 argument1, TValue2 argument2, string callbackName, object context = null)
|
||||
{
|
||||
if (callbacks.length == 0)
|
||||
return true;
|
||||
Profiling.Profiler.BeginSample(callbackName);
|
||||
callbacks.LockForChanges();
|
||||
for (var i = 0; i < callbacks.length; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (callbacks[i](argument1, argument2))
|
||||
{
|
||||
callbacks.UnlockForChanges();
|
||||
Profiling.Profiler.EndSample();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
if (context != null)
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks of '{context}'");
|
||||
else
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks");
|
||||
}
|
||||
}
|
||||
callbacks.UnlockForChanges();
|
||||
Profiling.Profiler.EndSample();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the given callbacks and also invokes any callback returned from the result of the first.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Action"/>
|
||||
/// <remarks>
|
||||
/// Allows an chaining up an additional, optional block of code to the original callback
|
||||
/// and allow the external code make the decision about whether this code should be executed.
|
||||
/// </remarks>
|
||||
public static void InvokeCallbacksSafe_AndInvokeReturnedActions<TValue>(
|
||||
ref CallbackArray<Func<TValue, Action>> callbacks, TValue argument,
|
||||
string callbackName, object context = null)
|
||||
{
|
||||
if (callbacks.length == 0)
|
||||
return;
|
||||
|
||||
Profiling.Profiler.BeginSample(callbackName);
|
||||
callbacks.LockForChanges();
|
||||
for (var i = 0; i < callbacks.length; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
callbacks[i](argument)?.Invoke();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
if (context != null)
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks of '{context}'");
|
||||
else
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks");
|
||||
}
|
||||
}
|
||||
callbacks.UnlockForChanges();
|
||||
Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the given callbacks and returns true if any of them returned a non-null result.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns false if every callback invocation returned null.
|
||||
/// </remarks>
|
||||
public static bool InvokeCallbacksSafe_AnyCallbackReturnsObject<TValue, TReturn>(
|
||||
ref CallbackArray<Func<TValue, TReturn>> callbacks, TValue argument,
|
||||
string callbackName, object context = null)
|
||||
{
|
||||
if (callbacks.length == 0)
|
||||
return false;
|
||||
|
||||
Profiling.Profiler.BeginSample(callbackName);
|
||||
callbacks.LockForChanges();
|
||||
for (var i = 0; i < callbacks.length; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ret = callbacks[i](argument);
|
||||
if (ret != null)
|
||||
{
|
||||
callbacks.UnlockForChanges();
|
||||
Profiling.Profiler.EndSample();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
if (context != null)
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks of '{context}'");
|
||||
else
|
||||
Debug.LogError($"{exception.GetType().Name} while executing '{callbackName}' callbacks");
|
||||
}
|
||||
}
|
||||
callbacks.UnlockForChanges();
|
||||
Profiling.Profiler.EndSample();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb83bbf668a324899963d1ea424c5ad0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide a format string to use when creating display strings for instances of the class.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
|
||||
public class DisplayStringFormatAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Format template string in the form of "{namedPart} verbatimText". All named parts enclosed in
|
||||
/// curly braces are replaced from context whereas other text is included as is.
|
||||
/// </summary>
|
||||
public string formatString { get; set; }
|
||||
|
||||
public DisplayStringFormatAttribute(string formatString)
|
||||
{
|
||||
this.formatString = formatString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2c401960c5dc624c8970b2bee0371dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Struct replacement for System.Collections.Bitfield.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We don't want the extra heap object just for keeping the header
|
||||
/// state of the bitfield. This struct directly embeds the header
|
||||
/// into the owner. Also doesn't allocate any array while length is
|
||||
/// less than or equal to 64 bits.
|
||||
/// </remarks>
|
||||
internal struct DynamicBitfield
|
||||
{
|
||||
public InlinedArray<ulong> array;
|
||||
public int length;
|
||||
|
||||
public void SetLength(int newLength)
|
||||
{
|
||||
// Don't touch array size if we don't have to. We're fine having a
|
||||
// larger array to work with if it's already in place.
|
||||
var ulongCount = BitCountToULongCount(newLength);
|
||||
if (array.length < ulongCount)
|
||||
array.SetLength(ulongCount);
|
||||
|
||||
length = newLength;
|
||||
}
|
||||
|
||||
public void SetBit(int bitIndex)
|
||||
{
|
||||
Debug.Assert(bitIndex >= 0);
|
||||
Debug.Assert(bitIndex < length);
|
||||
|
||||
array[bitIndex / 64] |= 1UL << (bitIndex % 64);
|
||||
}
|
||||
|
||||
public bool TestBit(int bitIndex)
|
||||
{
|
||||
Debug.Assert(bitIndex >= 0);
|
||||
Debug.Assert(bitIndex < length);
|
||||
|
||||
return (array[bitIndex / 64] & (1UL << (bitIndex % 64))) != 0;
|
||||
}
|
||||
|
||||
public void ClearBit(int bitIndex)
|
||||
{
|
||||
Debug.Assert(bitIndex >= 0);
|
||||
Debug.Assert(bitIndex < length);
|
||||
|
||||
array[bitIndex / 64] &= ~(1UL << (bitIndex % 64));
|
||||
}
|
||||
|
||||
public bool AnyBitIsSet()
|
||||
{
|
||||
for (var i = 0; i < array.length; ++i)
|
||||
{
|
||||
if (array[i] != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int BitCountToULongCount(int bitCount)
|
||||
{
|
||||
return (bitCount + 63) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d45ac8148fd4e264eb17df8586b09581
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class ExceptionHelpers
|
||||
{
|
||||
public static bool IsExceptionIndicatingBugInCode(this Exception exception)
|
||||
{
|
||||
Debug.Assert(exception != null, "Exception is null");
|
||||
|
||||
return exception is NullReferenceException ||
|
||||
exception is IndexOutOfRangeException ||
|
||||
exception is ArgumentException;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d717b0e790ee146dd93d1be641984b6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
178
Packages/com.unity.inputsystem/InputSystem/Utilities/FourCC.cs
Normal file
178
Packages/com.unity.inputsystem/InputSystem/Utilities/FourCC.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A four-character code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A four-character code is a struct containing four byte characters totalling a single <c>int</c>.
|
||||
/// FourCCs are frequently used in the input system to identify the format of data sent to or from
|
||||
/// the native backend representing events, input device state or commands sent to input devices.
|
||||
/// </remarks>
|
||||
public struct FourCC : IEquatable<FourCC>
|
||||
{
|
||||
private int m_Code;
|
||||
|
||||
/// <summary>
|
||||
/// Create a FourCC from the given integer.
|
||||
/// </summary>
|
||||
/// <param name="code">FourCC code represented as an <c>int</c>. Character order is
|
||||
/// little endian. "ABCD" is stored with A in the highest order 8 bits and D in the
|
||||
/// lowest order 8 bits.</param>
|
||||
/// <remarks>
|
||||
/// This method does not actually verify whether the four characters in the code
|
||||
/// are printable.
|
||||
/// </remarks>
|
||||
public FourCC(int code)
|
||||
{
|
||||
m_Code = code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FourCC from the given four characters.
|
||||
/// </summary>
|
||||
/// <param name="a">First character.</param>
|
||||
/// <param name="b">Second character.</param>
|
||||
/// <param name="c">Third character.</param>
|
||||
/// <param name="d">Fourth character.</param>
|
||||
public FourCC(char a, char b = ' ', char c = ' ', char d = ' ')
|
||||
{
|
||||
m_Code = (a << 24) | (b << 16) | (c << 8) | d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FourCC from the given string.
|
||||
/// </summary>
|
||||
/// <param name="str">A string with four characters or less but with at least one character.</param>
|
||||
/// <exception cref="ArgumentException"><paramref name="str"/> is empty or has more than four characters.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="str"/> is <c>null</c>.</exception>
|
||||
public FourCC(string str)
|
||||
: this()
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
|
||||
var length = str.Length;
|
||||
if (length < 1 || length > 4)
|
||||
throw new ArgumentException("FourCC string must be one to four characters long!", nameof(str));
|
||||
|
||||
var a = str[0];
|
||||
var b = length > 1 ? str[1] : ' ';
|
||||
var c = length > 2 ? str[2] : ' ';
|
||||
var d = length > 3 ? str[3] : ' ';
|
||||
|
||||
m_Code = (a << 24) | (b << 16) | (c << 8) | d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the given FourCC into an <c>int</c>.
|
||||
/// </summary>
|
||||
/// <param name="fourCC">A FourCC.</param>
|
||||
/// <returns>The four characters of the code packed into one <c>int</c>. Character order is
|
||||
/// little endian. "ABCD" is stored with A in the highest order 8 bits and D in the
|
||||
/// lowest order 8 bits.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator int(FourCC fourCC)
|
||||
{
|
||||
return fourCC.m_Code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the given <c>int</c> into a FourCC.
|
||||
/// </summary>
|
||||
/// <param name="i">FourCC code represented as an <c>int</c>. Character order is
|
||||
/// little endian. "ABCD" is stored with A in the highest order 8 bits and D in the
|
||||
/// lowest order 8 bits.</param>
|
||||
/// <returns>The FourCC converted from <paramref name="i"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator FourCC(int i)
|
||||
{
|
||||
return new FourCC(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the FourCC into a string in the form of "ABCD".
|
||||
/// </summary>
|
||||
/// <returns>String representation of the FourCC.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"{(char) (m_Code >> 24)}{(char) ((m_Code & 0xff0000) >> 16)}{(char) ((m_Code & 0xff00) >> 8)}{(char) (m_Code & 0xff)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two FourCCs for equality.
|
||||
/// </summary>
|
||||
/// <param name="other">Another FourCC.</param>
|
||||
/// <returns>True if the two FourCCs are equal, i.e. have the same exact
|
||||
/// character codes.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(FourCC other)
|
||||
{
|
||||
return m_Code == other.m_Code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the FourCC to the given object.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object. Can be null.</param>
|
||||
/// <returns>True if <paramref name="obj"/> is a FourCC that has the same
|
||||
/// character code sequence.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
return obj is FourCC cc && Equals(cc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a hash code for the FourCC.
|
||||
/// </summary>
|
||||
/// <returns>Simply returns the FourCC converted to an <c>int</c>.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return m_Code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two FourCCs for equality.
|
||||
/// </summary>
|
||||
/// <param name="left">First FourCC.</param>
|
||||
/// <param name="right">Second FourCC.</param>
|
||||
/// <returns>True if the two FourCCs are equal, i.e. have the same exact
|
||||
/// character codes.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator==(FourCC left, FourCC right)
|
||||
{
|
||||
return left.m_Code == right.m_Code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two FourCCs for inequality.
|
||||
/// </summary>
|
||||
/// <param name="left">First FourCC.</param>
|
||||
/// <param name="right">Second FourCC.</param>
|
||||
/// <returns>True if the two FourCCs are not equal, i.e. do not have the same exact
|
||||
/// character codes.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator!=(FourCC left, FourCC right)
|
||||
{
|
||||
return left.m_Code != right.m_Code;
|
||||
}
|
||||
|
||||
// Make annoying Microsoft code analyzer happy.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FourCC FromInt32(int i)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToInt32(FourCC fourCC)
|
||||
{
|
||||
return fourCC.m_Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff1bc3762d8741ff8f40d54da690efb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98a0df93c82e4369b500b62affa160db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
////TODO: goal should be to end up with this being internal
|
||||
|
||||
////TODO: instead of using string.Intern, put them in a custom table and allow passing them around as indices
|
||||
//// (this will probably also be useful for jobs)
|
||||
//// when this is implemented, also allow interning directly from Substrings
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps around a string to allow for faster case-insensitive string comparisons while
|
||||
/// preserving original casing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unlike <c>string</c>, InternedStrings can be compared with a quick <c>Object.ReferenceEquals</c>
|
||||
/// comparison and without actually comparing string contents.
|
||||
///
|
||||
/// Also, unlike <c>string</c>, the representation of an empty and a <c>null</c> string is identical.
|
||||
///
|
||||
/// Note that all string comparisons using InternedStrings are both case-insensitive and culture-insensitive.
|
||||
///
|
||||
/// There is a non-zero cost to creating an InternedString. The first time a new unique InternedString
|
||||
/// is encountered, there may also be a GC heap allocation.
|
||||
/// </remarks>
|
||||
public struct InternedString : IEquatable<InternedString>, IComparable<InternedString>
|
||||
{
|
||||
private readonly string m_StringOriginalCase;
|
||||
private readonly string m_StringLowerCase;
|
||||
|
||||
/// <summary>
|
||||
/// Length of the string in characters. Equivalent to <c>string.Length</c>.
|
||||
/// </summary>
|
||||
/// <value>Length of the string.</value>
|
||||
public int length => m_StringLowerCase?.Length ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the InternedString with the given string. Except if the string is <c>null</c>
|
||||
/// or empty, this requires an internal lookup (this is the reason the conversion from <c>string</c>
|
||||
/// to InternedString is not implicit).
|
||||
/// </summary>
|
||||
/// <param name="text">A string. Can be null.</param>
|
||||
/// <remarks>
|
||||
/// The InternedString preserves the original casing. Meaning that <see cref="ToString()"/> will
|
||||
/// return the string as it was supplied through <paramref name="text"/>. However, comparison
|
||||
/// between two InternedStrings is still always just a reference comparisons regardless of case
|
||||
/// and culture.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var lowerCase = new InternedString("text");
|
||||
/// var upperCase = new InternedString("TEXT");
|
||||
///
|
||||
/// // This is still just a quick reference comparison:
|
||||
/// if (lowerCase == upperCase)
|
||||
/// Debug.Log("True");
|
||||
///
|
||||
/// // But this prints the strings in their original casing.
|
||||
/// Debug.Log(lowerCase);
|
||||
/// Debug.Log(upperCase);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public InternedString(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
m_StringOriginalCase = null;
|
||||
m_StringLowerCase = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
////TODO: I think instead of string.Intern() this should use a custom weak-referenced intern table
|
||||
//// (this way we can also avoid the garbage from ToLower())
|
||||
m_StringOriginalCase = string.Intern(text);
|
||||
m_StringLowerCase = string.Intern(text.ToLower(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the string is empty, i.e. has a <see cref="length"/> of zero. If so, the
|
||||
/// InternedString corresponds to <c>default(InternedString)</c>.
|
||||
/// </summary>
|
||||
/// <returns>True if the string is empty.</returns>
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return m_StringLowerCase == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a lower-case version of the string.
|
||||
/// </summary>
|
||||
/// <returns>A lower-case version of the string.</returns>
|
||||
/// <remarks>
|
||||
/// InternedStrings internally always store a lower-case version which means that this
|
||||
/// method does not incur a GC heap allocation cost.
|
||||
/// </remarks>
|
||||
public string ToLower()
|
||||
{
|
||||
return m_StringLowerCase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the InternedString to given object.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object. If it is a <c>string</c>, performs a string comparison. If
|
||||
/// it is an InternedString, performs an InternedString-comparison. Otherwise returns false.</param>
|
||||
/// <returns>True if the InternedString is equal to <paramref name="obj"/>.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is InternedString other)
|
||||
return Equals(other);
|
||||
|
||||
if (obj is string str)
|
||||
{
|
||||
if (m_StringLowerCase == null)
|
||||
return string.IsNullOrEmpty(str);
|
||||
return string.Equals(m_StringLowerCase, str.ToLower(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two InternedStrings for equality. They are equal if, ignoring case and culture,
|
||||
/// their text is equal.
|
||||
/// </summary>
|
||||
/// <param name="other">Another InternedString.</param>
|
||||
/// <returns>True if the two InternedStrings are equal.</returns>
|
||||
/// <remarks>
|
||||
/// This operation is cheap and does not involve an actual string comparison. Instead,
|
||||
/// a simple <c>Object.ReferenceEquals</c> comparison is performed.
|
||||
/// </remarks>
|
||||
public bool Equals(InternedString other)
|
||||
{
|
||||
return ReferenceEquals(m_StringLowerCase, other.m_StringLowerCase);
|
||||
}
|
||||
|
||||
public int CompareTo(InternedString other)
|
||||
{
|
||||
return string.Compare(m_StringLowerCase, other.m_StringLowerCase,
|
||||
StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a hash code for the string. Equivalent to <c>string.GetHashCode</c>.
|
||||
/// </summary>
|
||||
/// <returns>A hash code.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (m_StringLowerCase == null)
|
||||
return 0;
|
||||
return m_StringLowerCase.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return m_StringOriginalCase ?? string.Empty;
|
||||
}
|
||||
|
||||
public static bool operator==(InternedString a, InternedString b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(InternedString a, InternedString b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator==(InternedString a, string b)
|
||||
{
|
||||
return string.Compare(a.m_StringLowerCase, b, StringComparison.InvariantCultureIgnoreCase) == 0;
|
||||
}
|
||||
|
||||
public static bool operator!=(InternedString a, string b)
|
||||
{
|
||||
return string.Compare(a.m_StringLowerCase, b, StringComparison.InvariantCultureIgnoreCase) != 0;
|
||||
}
|
||||
|
||||
public static bool operator==(string a, InternedString b)
|
||||
{
|
||||
return string.Compare(a, b.m_StringLowerCase, StringComparison.InvariantCultureIgnoreCase) == 0;
|
||||
}
|
||||
|
||||
public static bool operator!=(string a, InternedString b)
|
||||
{
|
||||
return string.Compare(a, b.m_StringLowerCase, StringComparison.InvariantCultureIgnoreCase) != 0;
|
||||
}
|
||||
|
||||
public static bool operator<(InternedString left, InternedString right)
|
||||
{
|
||||
return string.Compare(left.m_StringLowerCase, right.m_StringLowerCase,
|
||||
StringComparison.InvariantCultureIgnoreCase) < 0;
|
||||
}
|
||||
|
||||
public static bool operator>(InternedString left, InternedString right)
|
||||
{
|
||||
return string.Compare(left.m_StringLowerCase, right.m_StringLowerCase,
|
||||
StringComparison.InvariantCultureIgnoreCase) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the given InternedString back to a <c>string</c>. Equivalent to <see cref="ToString()"/>.
|
||||
/// </summary>
|
||||
/// <param name="str">An InternedString.</param>
|
||||
/// <returns>A string.</returns>
|
||||
public static implicit operator string(InternedString str)
|
||||
{
|
||||
return str.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 640e1e705a694e56835bacff3d7f7b4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,920 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A JSON parser that instead of turning a string in JSON format into a
|
||||
/// C# object graph, allows navigating the source text directly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This helper is most useful for avoiding a great many string and general object allocations
|
||||
/// that would happen when turning a JSON object into a C# object graph.
|
||||
/// </remarks>
|
||||
internal struct JsonParser
|
||||
{
|
||||
public JsonParser(string json)
|
||||
: this()
|
||||
{
|
||||
if (json == null)
|
||||
throw new ArgumentNullException(nameof(json));
|
||||
|
||||
m_Text = json;
|
||||
m_Length = json.Length;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_Position = 0;
|
||||
m_MatchAnyElementInArray = false;
|
||||
m_DryRun = false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (m_Text != null)
|
||||
return $"{m_Position}: {m_Text.Substring(m_Position)}";
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to the given property.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <remarks>
|
||||
/// This navigates from the current property.
|
||||
/// </remarks>
|
||||
public bool NavigateToProperty(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
|
||||
var pathLength = path.Length;
|
||||
var pathPosition = 0;
|
||||
|
||||
m_DryRun = true;
|
||||
if (!ParseToken('{'))
|
||||
return false;
|
||||
|
||||
while (m_Position < m_Length && pathPosition < pathLength)
|
||||
{
|
||||
// Find start of property name.
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length)
|
||||
return false;
|
||||
if (m_Text[m_Position] != '"')
|
||||
return false;
|
||||
++m_Position;
|
||||
|
||||
// Try to match single path component.
|
||||
var pathStartPosition = pathPosition;
|
||||
while (pathPosition < pathLength)
|
||||
{
|
||||
var ch = path[pathPosition];
|
||||
if (ch == '/' || ch == '[')
|
||||
break;
|
||||
|
||||
if (m_Text[m_Position] != ch)
|
||||
break;
|
||||
|
||||
++m_Position;
|
||||
++pathPosition;
|
||||
}
|
||||
|
||||
// See if we have a match.
|
||||
if (m_Position < m_Length && m_Text[m_Position] == '"' && (pathPosition >= pathLength || path[pathPosition] == '/' || path[pathPosition] == '['))
|
||||
{
|
||||
// Have matched a property name. Navigate to value.
|
||||
++m_Position;
|
||||
if (!SkipToValue())
|
||||
return false;
|
||||
|
||||
// Check if we have matched everything in the path.
|
||||
if (pathPosition >= pathLength)
|
||||
return true;
|
||||
if (path[pathPosition] == '/')
|
||||
{
|
||||
++pathPosition;
|
||||
if (!ParseToken('{'))
|
||||
return false;
|
||||
}
|
||||
else if (path[pathPosition] == '[')
|
||||
{
|
||||
++pathPosition;
|
||||
if (pathPosition == pathLength)
|
||||
throw new ArgumentException("Malformed JSON property path: " + path, nameof(path));
|
||||
if (path[pathPosition] == ']')
|
||||
{
|
||||
m_MatchAnyElementInArray = true;
|
||||
++pathPosition;
|
||||
if (pathPosition == pathLength)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
throw new NotImplementedException("Navigating to specific array element");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This property isn't it. Skip the property and its value and reset
|
||||
// to where we started in this iteration in the property path.
|
||||
|
||||
pathPosition = pathStartPosition;
|
||||
while (m_Position < m_Length && m_Text[m_Position] != '"')
|
||||
++m_Position;
|
||||
if (m_Position == m_Length || m_Text[m_Position] != '"')
|
||||
return false;
|
||||
++m_Position;
|
||||
if (!SkipToValue() || !ParseValue())
|
||||
return false;
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length || m_Text[m_Position] == '}' || m_Text[m_Position] != ',')
|
||||
return false;
|
||||
++m_Position;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the current property has a value matching <paramref name="expectedValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="expectedValue"></param>
|
||||
/// <returns></returns>
|
||||
public bool CurrentPropertyHasValueEqualTo(JsonValue expectedValue)
|
||||
{
|
||||
// Grab property value.
|
||||
var savedPosition = m_Position;
|
||||
m_DryRun = false;
|
||||
if (!ParseValue(out var propertyValue))
|
||||
{
|
||||
m_Position = savedPosition;
|
||||
return false;
|
||||
}
|
||||
m_Position = savedPosition;
|
||||
|
||||
// Match given value.
|
||||
var isMatch = false;
|
||||
if (propertyValue.type == JsonValueType.Array && m_MatchAnyElementInArray)
|
||||
{
|
||||
var array = propertyValue.arrayValue;
|
||||
for (var i = 0; !isMatch && i < array.Count; ++i)
|
||||
isMatch = array[i] == expectedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
isMatch = propertyValue == expectedValue;
|
||||
}
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
public bool ParseToken(char token)
|
||||
{
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length)
|
||||
return false;
|
||||
|
||||
if (m_Text[m_Position] != token)
|
||||
return false;
|
||||
|
||||
++m_Position;
|
||||
SkipWhitespace();
|
||||
|
||||
return m_Position < m_Length;
|
||||
}
|
||||
|
||||
public bool ParseValue()
|
||||
{
|
||||
return ParseValue(out var result);
|
||||
}
|
||||
|
||||
public bool ParseValue(out JsonValue result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length)
|
||||
return false;
|
||||
|
||||
var ch = m_Text[m_Position];
|
||||
switch (ch)
|
||||
{
|
||||
case '"':
|
||||
if (ParseStringValue(out result))
|
||||
return true;
|
||||
break;
|
||||
case '[':
|
||||
if (ParseArrayValue(out result))
|
||||
return true;
|
||||
break;
|
||||
case '{':
|
||||
if (ParseObjectValue(out result))
|
||||
return true;
|
||||
break;
|
||||
case 't':
|
||||
case 'f':
|
||||
if (ParseBooleanValue(out result))
|
||||
return true;
|
||||
break;
|
||||
case 'n':
|
||||
if (ParseNullValue(out result))
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
if (ParseNumber(out result))
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ParseStringValue(out JsonValue result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length || m_Text[m_Position] != '"')
|
||||
return false;
|
||||
++m_Position;
|
||||
|
||||
var startIndex = m_Position;
|
||||
var hasEscapes = false;
|
||||
|
||||
while (m_Position < m_Length)
|
||||
{
|
||||
var ch = m_Text[m_Position];
|
||||
if (ch == '\\')
|
||||
{
|
||||
++m_Position;
|
||||
if (m_Position == m_Length)
|
||||
break;
|
||||
hasEscapes = true;
|
||||
}
|
||||
else if (ch == '"')
|
||||
{
|
||||
++m_Position;
|
||||
|
||||
result = new JsonString
|
||||
{
|
||||
text = new Substring(m_Text, startIndex, m_Position - startIndex - 1),
|
||||
hasEscapes = hasEscapes
|
||||
};
|
||||
return true;
|
||||
}
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ParseArrayValue(out JsonValue result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length || m_Text[m_Position] != '[')
|
||||
return false;
|
||||
++m_Position;
|
||||
|
||||
if (m_Position == m_Length)
|
||||
return false;
|
||||
if (m_Text[m_Position] == ']')
|
||||
{
|
||||
// Empty array.
|
||||
result = new JsonValue { type = JsonValueType.Array };
|
||||
++m_Position;
|
||||
return true;
|
||||
}
|
||||
|
||||
List<JsonValue> values = null;
|
||||
if (!m_DryRun)
|
||||
values = new List<JsonValue>();
|
||||
|
||||
while (m_Position < m_Length)
|
||||
{
|
||||
if (!ParseValue(out var value))
|
||||
return false;
|
||||
if (!m_DryRun)
|
||||
values.Add(value);
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length)
|
||||
return false;
|
||||
var ch = m_Text[m_Position];
|
||||
if (ch == ']')
|
||||
{
|
||||
++m_Position;
|
||||
if (!m_DryRun)
|
||||
result = values;
|
||||
return true;
|
||||
}
|
||||
if (ch == ',')
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ParseObjectValue(out JsonValue result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
if (!ParseToken('{'))
|
||||
return false;
|
||||
if (m_Position < m_Length && m_Text[m_Position] == '}')
|
||||
{
|
||||
result = new JsonValue { type = JsonValueType.Object };
|
||||
++m_Position;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (m_Position < m_Length)
|
||||
{
|
||||
if (!ParseStringValue(out var propertyName))
|
||||
return false;
|
||||
|
||||
if (!SkipToValue())
|
||||
return false;
|
||||
|
||||
if (!ParseValue(out var propertyValue))
|
||||
return false;
|
||||
|
||||
if (!m_DryRun)
|
||||
throw new NotImplementedException();
|
||||
|
||||
SkipWhitespace();
|
||||
if (m_Position < m_Length && m_Text[m_Position] == '}')
|
||||
{
|
||||
if (!m_DryRun)
|
||||
throw new NotImplementedException();
|
||||
++m_Position;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ParseNumber(out JsonValue result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length)
|
||||
return false;
|
||||
|
||||
var negative = false;
|
||||
var haveFractionalPart = false;
|
||||
var integralPart = 0L;
|
||||
var fractionalPart = 0.0;
|
||||
var fractionalDivisor = 10.0;
|
||||
var exponent = 0;
|
||||
|
||||
// Parse sign.
|
||||
if (m_Text[m_Position] == '-')
|
||||
{
|
||||
negative = true;
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
|
||||
return false;
|
||||
|
||||
// Parse integral part.
|
||||
while (m_Position < m_Length)
|
||||
{
|
||||
var ch = m_Text[m_Position];
|
||||
if (ch == '.')
|
||||
break;
|
||||
if (ch < '0' || ch > '9')
|
||||
break;
|
||||
integralPart = integralPart * 10 + ch - '0';
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
// Parse fractional part.
|
||||
if (m_Position < m_Length && m_Text[m_Position] == '.')
|
||||
{
|
||||
haveFractionalPart = true;
|
||||
++m_Position;
|
||||
if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
|
||||
return false;
|
||||
while (m_Position < m_Length)
|
||||
{
|
||||
var ch = m_Text[m_Position];
|
||||
if (ch < '0' || ch > '9')
|
||||
break;
|
||||
fractionalPart = (ch - '0') / fractionalDivisor + fractionalPart;
|
||||
fractionalDivisor *= 10;
|
||||
++m_Position;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Position < m_Length && (m_Text[m_Position] == 'e' || m_Text[m_Position] == 'E'))
|
||||
{
|
||||
++m_Position;
|
||||
var isNegative = false;
|
||||
if (m_Position < m_Length && m_Text[m_Position] == '-')
|
||||
{
|
||||
isNegative = true;
|
||||
++m_Position;
|
||||
}
|
||||
else if (m_Position < m_Length && m_Text[m_Position] == '+')
|
||||
{
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
var multiplier = 1;
|
||||
while (m_Position < m_Length && char.IsDigit(m_Text[m_Position]))
|
||||
{
|
||||
var digit = m_Text[m_Position] - '0';
|
||||
exponent *= multiplier;
|
||||
exponent += digit;
|
||||
multiplier *= 10;
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
if (isNegative)
|
||||
exponent *= -1;
|
||||
}
|
||||
|
||||
if (!m_DryRun)
|
||||
{
|
||||
if (!haveFractionalPart && exponent == 0)
|
||||
{
|
||||
if (negative)
|
||||
result = -integralPart;
|
||||
else
|
||||
result = integralPart;
|
||||
}
|
||||
else
|
||||
{
|
||||
float value;
|
||||
if (negative)
|
||||
value = (float)-(integralPart + fractionalPart);
|
||||
else
|
||||
value = (float)(integralPart + fractionalPart);
|
||||
if (exponent != 0)
|
||||
value *= Mathf.Pow(10, exponent);
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ParseBooleanValue(out JsonValue result)
|
||||
{
|
||||
SkipWhitespace();
|
||||
if (SkipString("true"))
|
||||
{
|
||||
result = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SkipString("false"))
|
||||
{
|
||||
result = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ParseNullValue(out JsonValue result)
|
||||
{
|
||||
result = default;
|
||||
return SkipString("null");
|
||||
}
|
||||
|
||||
public bool SkipToValue()
|
||||
{
|
||||
SkipWhitespace();
|
||||
if (m_Position == m_Length || m_Text[m_Position] != ':')
|
||||
return false;
|
||||
++m_Position;
|
||||
SkipWhitespace();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SkipString(string text)
|
||||
{
|
||||
SkipWhitespace();
|
||||
var length = text.Length;
|
||||
if (m_Position + length >= m_Length)
|
||||
return false;
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
if (m_Text[m_Position + i] != text[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Position += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SkipWhitespace()
|
||||
{
|
||||
while (m_Position < m_Length && char.IsWhiteSpace(m_Text[m_Position]))
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
public bool isAtEnd => m_Position >= m_Length;
|
||||
|
||||
private readonly string m_Text;
|
||||
private readonly int m_Length;
|
||||
private int m_Position;
|
||||
private bool m_MatchAnyElementInArray;
|
||||
private bool m_DryRun;
|
||||
|
||||
public enum JsonValueType
|
||||
{
|
||||
None,
|
||||
Bool,
|
||||
Real,
|
||||
Integer,
|
||||
String,
|
||||
Array,
|
||||
Object,
|
||||
Any,
|
||||
}
|
||||
|
||||
public struct JsonString : IEquatable<JsonString>
|
||||
{
|
||||
public Substring text;
|
||||
public bool hasEscapes;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (!hasEscapes)
|
||||
return text.ToString();
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var length = text.length;
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
var ch = text[i];
|
||||
if (ch == '\\')
|
||||
{
|
||||
++i;
|
||||
if (i == length)
|
||||
break;
|
||||
ch = text[i];
|
||||
}
|
||||
builder.Append(ch);
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public bool Equals(JsonString other)
|
||||
{
|
||||
if (hasEscapes == other.hasEscapes)
|
||||
return Substring.Compare(text, other.text, StringComparison.InvariantCultureIgnoreCase) == 0;
|
||||
|
||||
var thisLength = text.length;
|
||||
var otherLength = other.text.length;
|
||||
|
||||
int thisIndex = 0, otherIndex = 0;
|
||||
for (; thisIndex < thisLength && otherIndex < otherLength; ++thisIndex, ++otherIndex)
|
||||
{
|
||||
var thisChar = text[thisIndex];
|
||||
var otherChar = other.text[otherIndex];
|
||||
|
||||
if (thisChar == '\\')
|
||||
{
|
||||
++thisIndex;
|
||||
if (thisIndex == thisLength)
|
||||
return false;
|
||||
thisChar = text[thisIndex];
|
||||
}
|
||||
|
||||
if (otherChar == '\\')
|
||||
{
|
||||
++otherIndex;
|
||||
if (otherIndex == otherLength)
|
||||
return false;
|
||||
otherChar = other.text[otherIndex];
|
||||
}
|
||||
|
||||
if (char.ToUpperInvariant(thisChar) != char.ToUpperInvariant(otherChar))
|
||||
return false;
|
||||
}
|
||||
|
||||
return thisIndex == thisLength && otherIndex == otherLength;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is JsonString other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (text.GetHashCode() * 397) ^ hasEscapes.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator==(JsonString left, JsonString right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator!=(JsonString left, JsonString right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public static implicit operator JsonString(string str)
|
||||
{
|
||||
return new JsonString { text = str };
|
||||
}
|
||||
}
|
||||
|
||||
public struct JsonValue : IEquatable<JsonValue>
|
||||
{
|
||||
public JsonValueType type;
|
||||
public bool boolValue;
|
||||
public double realValue;
|
||||
public long integerValue;
|
||||
public JsonString stringValue;
|
||||
public List<JsonValue> arrayValue; // Allocates.
|
||||
public Dictionary<string, JsonValue> objectValue; // Allocates.
|
||||
public object anyValue;
|
||||
|
||||
public bool ToBoolean()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case JsonValueType.Bool: return boolValue;
|
||||
case JsonValueType.Integer: return integerValue != 0;
|
||||
case JsonValueType.Real: return NumberHelpers.Approximately(0, realValue);
|
||||
case JsonValueType.String: return Convert.ToBoolean(ToString());
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public long ToInteger()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case JsonValueType.Bool: return boolValue ? 1 : 0;
|
||||
case JsonValueType.Integer: return integerValue;
|
||||
case JsonValueType.Real: return (long)realValue;
|
||||
case JsonValueType.String: return Convert.ToInt64(ToString());
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public double ToDouble()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case JsonValueType.Bool: return boolValue ? 1 : 0;
|
||||
case JsonValueType.Integer: return integerValue;
|
||||
case JsonValueType.Real: return realValue;
|
||||
case JsonValueType.String: return Convert.ToSingle(ToString());
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case JsonValueType.None: return "null";
|
||||
case JsonValueType.Bool: return boolValue.ToString();
|
||||
case JsonValueType.Integer: return integerValue.ToString(CultureInfo.InvariantCulture);
|
||||
case JsonValueType.Real: return realValue.ToString(CultureInfo.InvariantCulture);
|
||||
case JsonValueType.String: return stringValue.ToString();
|
||||
case JsonValueType.Array:
|
||||
if (arrayValue == null)
|
||||
return "[]";
|
||||
return $"[{string.Join(",", arrayValue.Select(x => x.ToString()))}]";
|
||||
case JsonValueType.Object:
|
||||
if (objectValue == null)
|
||||
return "{}";
|
||||
var elements = objectValue.Select(pair => $"\"{pair.Key}\" : \"{pair.Value}\"");
|
||||
return $"{{{string.Join(",", elements)}}}";
|
||||
case JsonValueType.Any: return anyValue.ToString();
|
||||
}
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(bool val)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.Bool,
|
||||
boolValue = val
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(long val)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.Integer,
|
||||
integerValue = val
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(double val)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.Real,
|
||||
realValue = val
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(string str)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.String,
|
||||
stringValue = new JsonString { text = str }
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(JsonString str)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.String,
|
||||
stringValue = str
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(List<JsonValue> array)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.Array,
|
||||
arrayValue = array
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(Dictionary<string, JsonValue> obj)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.Object,
|
||||
objectValue = obj
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator JsonValue(Enum val)
|
||||
{
|
||||
return new JsonValue
|
||||
{
|
||||
type = JsonValueType.Any,
|
||||
anyValue = val
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(JsonValue other)
|
||||
{
|
||||
// Default comparisons.
|
||||
if (type == other.type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case JsonValueType.None: return true;
|
||||
case JsonValueType.Bool: return boolValue == other.boolValue;
|
||||
case JsonValueType.Integer: return integerValue == other.integerValue;
|
||||
case JsonValueType.Real: return NumberHelpers.Approximately(realValue, other.realValue);
|
||||
case JsonValueType.String: return stringValue == other.stringValue;
|
||||
case JsonValueType.Object: throw new NotImplementedException();
|
||||
case JsonValueType.Array: throw new NotImplementedException();
|
||||
case JsonValueType.Any: return anyValue.Equals(other.anyValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// anyValue-based comparisons.
|
||||
if (anyValue != null)
|
||||
return Equals(anyValue, other);
|
||||
if (other.anyValue != null)
|
||||
return Equals(other.anyValue, this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool Equals(object obj, JsonValue value)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (obj is Regex regex)
|
||||
return regex.IsMatch(value.ToString());
|
||||
if (obj is string str)
|
||||
{
|
||||
switch (value.type)
|
||||
{
|
||||
case JsonValueType.String: return value.stringValue == str;
|
||||
case JsonValueType.Integer: return long.TryParse(str, out var si) && si == value.integerValue;
|
||||
case JsonValueType.Real:
|
||||
return double.TryParse(str, out var sf) && NumberHelpers.Approximately(sf, value.realValue);
|
||||
case JsonValueType.Bool:
|
||||
if (value.boolValue)
|
||||
return str == "True" || str == "true" || str == "1";
|
||||
return str == "False" || str == "false" || str == "0";
|
||||
}
|
||||
}
|
||||
if (obj is float f)
|
||||
{
|
||||
if (value.type == JsonValueType.Real)
|
||||
return NumberHelpers.Approximately(f, value.realValue);
|
||||
if (value.type == JsonValueType.String)
|
||||
return float.TryParse(value.ToString(), out var otherF) && Mathf.Approximately(f, otherF);
|
||||
}
|
||||
if (obj is double d)
|
||||
{
|
||||
if (value.type == JsonValueType.Real)
|
||||
return NumberHelpers.Approximately(d, value.realValue);
|
||||
if (value.type == JsonValueType.String)
|
||||
return double.TryParse(value.ToString(), out var otherD) &&
|
||||
NumberHelpers.Approximately(d, otherD);
|
||||
}
|
||||
if (obj is int i)
|
||||
{
|
||||
if (value.type == JsonValueType.Integer)
|
||||
return i == value.integerValue;
|
||||
if (value.type == JsonValueType.String)
|
||||
return int.TryParse(value.ToString(), out var otherI) && i == otherI;
|
||||
}
|
||||
if (obj is long l)
|
||||
{
|
||||
if (value.type == JsonValueType.Integer)
|
||||
return l == value.integerValue;
|
||||
if (value.type == JsonValueType.String)
|
||||
return long.TryParse(value.ToString(), out var otherL) && l == otherL;
|
||||
}
|
||||
if (obj is bool b)
|
||||
{
|
||||
if (value.type == JsonValueType.Bool)
|
||||
return b == value.boolValue;
|
||||
if (value.type == JsonValueType.String)
|
||||
{
|
||||
if (b)
|
||||
return value.stringValue == "true" || value.stringValue == "True" ||
|
||||
value.stringValue == "1";
|
||||
return value.stringValue == "false" || value.stringValue == "False" ||
|
||||
value.stringValue == "0";
|
||||
}
|
||||
}
|
||||
// NOTE: The enum-based comparisons allocate both on the Convert.ToInt64() and Enum.GetName() path. I've found
|
||||
// no way to do either comparison in a way that does not allocate.
|
||||
if (obj is Enum)
|
||||
{
|
||||
if (value.type == JsonValueType.Integer)
|
||||
return Convert.ToInt64(obj) == value.integerValue;
|
||||
if (value.type == JsonValueType.String)
|
||||
return value.stringValue == Enum.GetName(obj.GetType(), obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is JsonValue other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int)type;
|
||||
hashCode = (hashCode * 397) ^ boolValue.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ realValue.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ integerValue.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ stringValue.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ (arrayValue != null ? arrayValue.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (objectValue != null ? objectValue.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (anyValue != null ? anyValue.GetHashCode() : 0);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator==(JsonValue left, JsonValue right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator!=(JsonValue left, JsonValue right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e082355c53c54d19a09c1d4f7629360
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,594 @@
|
||||
using System;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static unsafe class MemoryHelpers
|
||||
{
|
||||
public struct BitRegion
|
||||
{
|
||||
public uint bitOffset;
|
||||
public uint sizeInBits;
|
||||
|
||||
public bool isEmpty => sizeInBits == 0;
|
||||
|
||||
public BitRegion(uint bitOffset, uint sizeInBits)
|
||||
{
|
||||
this.bitOffset = bitOffset;
|
||||
this.sizeInBits = sizeInBits;
|
||||
}
|
||||
|
||||
public BitRegion(uint byteOffset, uint bitOffset, uint sizeInBits)
|
||||
{
|
||||
this.bitOffset = byteOffset * 8 + bitOffset;
|
||||
this.sizeInBits = sizeInBits;
|
||||
}
|
||||
|
||||
public BitRegion Overlap(BitRegion other)
|
||||
{
|
||||
////REVIEW: too many branches; this can probably be done much smarter
|
||||
|
||||
var thisEnd = bitOffset + sizeInBits;
|
||||
var otherEnd = other.bitOffset + other.sizeInBits;
|
||||
|
||||
if (thisEnd <= other.bitOffset || otherEnd <= bitOffset)
|
||||
return default;
|
||||
|
||||
var end = Math.Min(thisEnd, otherEnd);
|
||||
var start = Math.Max(bitOffset, other.bitOffset);
|
||||
|
||||
return new BitRegion(start, end - start);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Compare(void* ptr1, void* ptr2, BitRegion region)
|
||||
{
|
||||
if (region.sizeInBits == 1)
|
||||
return ReadSingleBit(ptr1, region.bitOffset) == ReadSingleBit(ptr2, region.bitOffset);
|
||||
return MemCmpBitRegion(ptr1, ptr2, region.bitOffset, region.sizeInBits);
|
||||
}
|
||||
|
||||
public static uint ComputeFollowingByteOffset(uint byteOffset, uint sizeInBits)
|
||||
{
|
||||
return (uint)(byteOffset + sizeInBits / 8 + (sizeInBits % 8 > 0 ? 1 : 0));
|
||||
}
|
||||
|
||||
public static void WriteSingleBit(void* ptr, uint bitOffset, bool value)
|
||||
{
|
||||
var byteOffset = bitOffset >> 3;
|
||||
bitOffset &= 7;
|
||||
if (value)
|
||||
*((byte*)ptr + byteOffset) |= (byte)(1U << (int)bitOffset);
|
||||
else
|
||||
*((byte*)ptr + byteOffset) &= (byte)~(1U << (int)bitOffset);
|
||||
}
|
||||
|
||||
public static bool ReadSingleBit(void* ptr, uint bitOffset)
|
||||
{
|
||||
var byteOffset = bitOffset >> 3;
|
||||
bitOffset &= 7;
|
||||
return (*((byte*)ptr + byteOffset) & (1U << (int)bitOffset)) != 0;
|
||||
}
|
||||
|
||||
public static void MemCpyBitRegion(void* destination, void* source, uint bitOffset, uint bitCount)
|
||||
{
|
||||
var destPtr = (byte*)destination;
|
||||
var sourcePtr = (byte*)source;
|
||||
|
||||
// If we're offset by more than a byte, adjust our pointers.
|
||||
if (bitOffset >= 8)
|
||||
{
|
||||
var skipBytes = bitOffset / 8;
|
||||
destPtr += skipBytes;
|
||||
sourcePtr += skipBytes;
|
||||
bitOffset %= 8;
|
||||
}
|
||||
|
||||
// Copy unaligned prefix, if any.
|
||||
if (bitOffset > 0)
|
||||
{
|
||||
var byteMask = 0xFF << (int)bitOffset;
|
||||
if (bitCount + bitOffset < 8)
|
||||
byteMask &= 0xFF >> (int)(8 - (bitCount + bitOffset));
|
||||
|
||||
*destPtr = (byte)(((*destPtr & ~byteMask) | (*sourcePtr & byteMask)) & 0xFF);
|
||||
|
||||
// If the total length of the memory region is equal or less than a byte,
|
||||
// we're done.
|
||||
if (bitCount + bitOffset <= 8)
|
||||
return;
|
||||
|
||||
++destPtr;
|
||||
++sourcePtr;
|
||||
|
||||
bitCount -= 8 - bitOffset;
|
||||
}
|
||||
|
||||
// Copy contiguous bytes in-between, if any.
|
||||
var byteCount = bitCount / 8;
|
||||
if (byteCount >= 1)
|
||||
UnsafeUtility.MemCpy(destPtr, sourcePtr, byteCount);
|
||||
|
||||
// Copy unaligned suffix, if any.
|
||||
var remainingBitCount = bitCount % 8;
|
||||
if (remainingBitCount > 0)
|
||||
{
|
||||
destPtr += byteCount;
|
||||
sourcePtr += byteCount;
|
||||
|
||||
// We want the lowest remaining bits.
|
||||
var byteMask = 0xFF >> (int)(8 - remainingBitCount);
|
||||
|
||||
*destPtr = (byte)(((*destPtr & ~byteMask) | (*sourcePtr & byteMask)) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two memory regions that may be offset by a bit count and have a length expressed
|
||||
/// in bits.
|
||||
/// </summary>
|
||||
/// <param name="ptr1">Pointer to start of first memory region.</param>
|
||||
/// <param name="ptr2">Pointer to start of second memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from each of the pointers to the start of the memory region to compare.</param>
|
||||
/// <param name="bitCount">Number of bits to compare in the memory region.</param>
|
||||
/// <param name="mask">If not null, only compare bits set in the mask. This allows comparing two memory regions while
|
||||
/// ignoring specific bits.</param>
|
||||
/// <returns>True if the two memory regions are identical, false otherwise.</returns>
|
||||
public static bool MemCmpBitRegion(void* ptr1, void* ptr2, uint bitOffset, uint bitCount, void* mask = null)
|
||||
{
|
||||
var bytePtr1 = (byte*)ptr1;
|
||||
var bytePtr2 = (byte*)ptr2;
|
||||
var maskPtr = (byte*)mask;
|
||||
|
||||
// If we're offset by more than a byte, adjust our pointers.
|
||||
if (bitOffset >= 8)
|
||||
{
|
||||
var skipBytes = bitOffset / 8;
|
||||
bytePtr1 += skipBytes;
|
||||
bytePtr2 += skipBytes;
|
||||
if (maskPtr != null)
|
||||
maskPtr += skipBytes;
|
||||
bitOffset %= 8;
|
||||
}
|
||||
|
||||
// Compare unaligned prefix, if any.
|
||||
if (bitOffset > 0)
|
||||
{
|
||||
// If the total length of the memory region is less than a byte, we need
|
||||
// to mask out parts of the bits we're reading.
|
||||
var byteMask = 0xFF << (int)bitOffset;
|
||||
if (bitCount + bitOffset < 8)
|
||||
byteMask &= 0xFF >> (int)(8 - (bitCount + bitOffset));
|
||||
|
||||
if (maskPtr != null)
|
||||
{
|
||||
byteMask &= *maskPtr;
|
||||
++maskPtr;
|
||||
}
|
||||
|
||||
var byte1 = *bytePtr1 & byteMask;
|
||||
var byte2 = *bytePtr2 & byteMask;
|
||||
|
||||
if (byte1 != byte2)
|
||||
return false;
|
||||
|
||||
// If the total length of the memory region is equal or less than a byte,
|
||||
// we're done.
|
||||
if (bitCount + bitOffset <= 8)
|
||||
return true;
|
||||
|
||||
++bytePtr1;
|
||||
++bytePtr2;
|
||||
|
||||
bitCount -= 8 - bitOffset;
|
||||
}
|
||||
|
||||
// Compare contiguous bytes in-between, if any.
|
||||
var byteCount = bitCount / 8;
|
||||
if (byteCount >= 1)
|
||||
{
|
||||
if (maskPtr != null)
|
||||
{
|
||||
////REVIEW: could go int by int here for as long as we can
|
||||
// Have to go byte-by-byte in order to apply the masking.
|
||||
for (var i = 0; i < byteCount; ++i)
|
||||
{
|
||||
var byte1 = bytePtr1[i];
|
||||
var byte2 = bytePtr2[i];
|
||||
var byteMask = maskPtr[i];
|
||||
|
||||
if ((byte1 & byteMask) != (byte2 & byteMask))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UnsafeUtility.MemCmp(bytePtr1, bytePtr2, byteCount) != 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare unaligned suffix, if any.
|
||||
var remainingBitCount = bitCount % 8;
|
||||
if (remainingBitCount > 0)
|
||||
{
|
||||
bytePtr1 += byteCount;
|
||||
bytePtr2 += byteCount;
|
||||
|
||||
// We want the lowest remaining bits.
|
||||
var byteMask = 0xFF >> (int)(8 - remainingBitCount);
|
||||
|
||||
if (maskPtr != null)
|
||||
{
|
||||
maskPtr += byteCount;
|
||||
byteMask &= *maskPtr;
|
||||
}
|
||||
|
||||
var byte1 = *bytePtr1 & byteMask;
|
||||
var byte2 = *bytePtr2 & byteMask;
|
||||
|
||||
if (byte1 != byte2)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void MemSet(void* destination, int numBytes, byte value)
|
||||
{
|
||||
var to = (byte*)destination;
|
||||
var pos = 0;
|
||||
|
||||
unchecked
|
||||
{
|
||||
// 64bit blocks.
|
||||
#if UNITY_64
|
||||
while (numBytes >= 8)
|
||||
{
|
||||
*(ulong*)&to[pos] = ((ulong)value << 56) | ((ulong)value << 48) | ((ulong)value << 40) | ((ulong)value << 32)
|
||||
| ((ulong)value << 24) | ((ulong)value << 16) | ((ulong)value << 8) | value;
|
||||
numBytes -= 8;
|
||||
pos += 8;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 32bit blocks.
|
||||
while (numBytes >= 4)
|
||||
{
|
||||
*(uint*)&to[pos] = ((uint)value << 24) | ((uint)value << 16) | ((uint)value << 8) | value;
|
||||
numBytes -= 4;
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
// Remaining bytes.
|
||||
while (numBytes > 0)
|
||||
{
|
||||
to[pos] = value;
|
||||
numBytes -= 1;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from <paramref name="source"/> to <paramref name="destination"/> all the bits that
|
||||
/// ARE set in <paramref name="mask"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">Memory to copy to.</param>
|
||||
/// <param name="source">Memory to copy from.</param>
|
||||
/// <param name="numBytes">Number of bytes to copy.</param>
|
||||
/// <param name="mask">Bitmask that determines which bits to copy. Bits that are set WILL be copied.</param>
|
||||
public static void MemCpyMasked(void* destination, void* source, int numBytes, void* mask)
|
||||
{
|
||||
var from = (byte*)source;
|
||||
var to = (byte*)destination;
|
||||
var bits = (byte*)mask;
|
||||
var pos = 0;
|
||||
|
||||
unchecked
|
||||
{
|
||||
// Copy 64bit blocks.
|
||||
#if UNITY_64
|
||||
while (numBytes >= 8)
|
||||
{
|
||||
*(ulong*)(to + pos) &= ~*(ulong*)(bits + pos); // Preserve unmasked bits.
|
||||
*(ulong*)(to + pos) |= *(ulong*)(from + pos) & *(ulong*)(bits + pos); // Copy masked bits.
|
||||
numBytes -= 8;
|
||||
pos += 8;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Copy 32bit blocks.
|
||||
while (numBytes >= 4)
|
||||
{
|
||||
*(uint*)(to + pos) &= ~*(uint*)(bits + pos); // Preserve unmasked bits.
|
||||
*(uint*)(to + pos) |= *(uint*)(from + pos) & *(uint*)(bits + pos); // Copy masked bits.
|
||||
numBytes -= 4;
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
// Copy remaining bytes.
|
||||
while (numBytes > 0)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
to[pos] &= (byte)~bits[pos]; // Preserve unmasked bits.
|
||||
to[pos] |= (byte)(from[pos] & bits[pos]); // Copy masked bits.
|
||||
}
|
||||
numBytes -= 1;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bits memory region as unsigned int, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <returns>Read unsigned integer.</returns>
|
||||
public static uint ReadMultipleBitsAsUInt(void* ptr, uint bitOffset, uint bitCount)
|
||||
{
|
||||
if (ptr == null)
|
||||
throw new ArgumentNullException(nameof(ptr));
|
||||
if (bitCount > sizeof(int) * 8)
|
||||
throw new ArgumentException("Trying to read more than 32 bits as int", nameof(bitCount));
|
||||
|
||||
// Shift the pointer up on larger bitmasks and retry.
|
||||
if (bitOffset > 32)
|
||||
{
|
||||
var newBitOffset = (int)bitOffset % 32;
|
||||
var intOffset = ((int)bitOffset - newBitOffset) / 32;
|
||||
ptr = (byte*)ptr + (intOffset * 4);
|
||||
bitOffset = (uint)newBitOffset;
|
||||
}
|
||||
|
||||
// Bits out of byte.
|
||||
if (bitOffset + bitCount <= 8)
|
||||
{
|
||||
var value = *(byte*)ptr;
|
||||
value >>= (int)bitOffset;
|
||||
var mask = 0xFFu >> (8 - (int)bitCount);
|
||||
return value & mask;
|
||||
}
|
||||
|
||||
// Bits out of short.
|
||||
if (bitOffset + bitCount <= 16)
|
||||
{
|
||||
var value = *(ushort*)ptr;
|
||||
value >>= (int)bitOffset;
|
||||
var mask = 0xFFFFu >> (16 - (int)bitCount);
|
||||
return value & mask;
|
||||
}
|
||||
|
||||
// Bits out of int.
|
||||
if (bitOffset + bitCount <= 32)
|
||||
{
|
||||
var value = *(uint*)ptr;
|
||||
value >>= (int)bitOffset;
|
||||
var mask = 0xFFFFFFFFu >> (32 - (int)bitCount);
|
||||
return value & mask;
|
||||
}
|
||||
|
||||
throw new NotImplementedException("Reading int straddling int boundary");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes unsigned int as bits to memory region, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <param name="value">Value to write.</param>
|
||||
public static void WriteUIntAsMultipleBits(void* ptr, uint bitOffset, uint bitCount, uint value)
|
||||
{
|
||||
if (ptr == null)
|
||||
throw new ArgumentNullException(nameof(ptr));
|
||||
if (bitCount > sizeof(int) * 8)
|
||||
throw new ArgumentException("Trying to write more than 32 bits as int", nameof(bitCount));
|
||||
|
||||
// Shift the pointer up on larger bitmasks and retry.
|
||||
if (bitOffset > 32)
|
||||
{
|
||||
var newBitOffset = (int)bitOffset % 32;
|
||||
var intOffset = ((int)bitOffset - newBitOffset) / 32;
|
||||
ptr = (byte*)ptr + (intOffset * 4);
|
||||
bitOffset = (uint)newBitOffset;
|
||||
}
|
||||
|
||||
// Bits out of byte.
|
||||
if (bitOffset + bitCount <= 8)
|
||||
{
|
||||
var byteValue = (byte)value;
|
||||
byteValue <<= (int)bitOffset;
|
||||
var mask = ~((0xFFU >> (8 - (int)bitCount)) << (int)bitOffset);
|
||||
*(byte*)ptr = (byte)((*(byte*)ptr & mask) | byteValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bits out of short.
|
||||
if (bitOffset + bitCount <= 16)
|
||||
{
|
||||
var ushortValue = (ushort)value;
|
||||
ushortValue <<= (int)bitOffset;
|
||||
var mask = ~((0xFFFFU >> (16 - (int)bitCount)) << (int)bitOffset);
|
||||
*(ushort*)ptr = (ushort)((*(ushort*)ptr & mask) | ushortValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bits out of int.
|
||||
if (bitOffset + bitCount <= 32)
|
||||
{
|
||||
var uintValue = (uint)value;
|
||||
uintValue <<= (int)bitOffset;
|
||||
var mask = ~((0xFFFFFFFFU >> (32 - (int)bitCount)) << (int)bitOffset);
|
||||
*(uint*)ptr = (*(uint*)ptr & mask) | uintValue;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotImplementedException("Writing int straddling int boundary");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bits memory region as two's complement integer, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// For example reading 0xff as 8 bits will result in -1.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <returns>Read integer.</returns>
|
||||
public static int ReadTwosComplementMultipleBitsAsInt(void* ptr, uint bitOffset, uint bitCount)
|
||||
{
|
||||
// int is already represented as two's complement
|
||||
return (int)ReadMultipleBitsAsUInt(ptr, bitOffset, bitCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes bits memory region as two's complement integer, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <param name="value">Value to write.</param>
|
||||
public static void WriteIntAsTwosComplementMultipleBits(void* ptr, uint bitOffset, uint bitCount, int value)
|
||||
{
|
||||
// int is already represented as two's complement, so write as-is
|
||||
WriteUIntAsMultipleBits(ptr, bitOffset, bitCount, (uint)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bits memory region as excess-K integer where K is set to (2^bitCount)/2, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// For example reading 0 as 8 bits will result in -128. Reading 0xff as 8 bits will result in 127.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <returns>Read integer.</returns>
|
||||
public static int ReadExcessKMultipleBitsAsInt(void* ptr, uint bitOffset, uint bitCount)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Signed_number_representations#Offset_binary
|
||||
var value = (long)ReadMultipleBitsAsUInt(ptr, bitOffset, bitCount);
|
||||
var halfMax = (long)((1UL << (int)bitCount) / 2);
|
||||
return (int)(value - halfMax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes bits memory region as excess-K integer where K is set to (2^bitCount)/2, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <param name="value">Value to write.</param>
|
||||
public static void WriteIntAsExcessKMultipleBits(void* ptr, uint bitOffset, uint bitCount, int value)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Signed_number_representations#Offset_binary
|
||||
var halfMax = (long)((1UL << (int)bitCount) / 2);
|
||||
var unsignedValue = halfMax + value;
|
||||
WriteUIntAsMultipleBits(ptr, bitOffset, bitCount, (uint)unsignedValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bits memory region as normalized unsigned integer, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// For example reading 0 as 8 bits will result in 0.0f. Reading 0xff as 8 bits will result in 1.0f.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <returns>Normalized unsigned integer.</returns>
|
||||
public static float ReadMultipleBitsAsNormalizedUInt(void* ptr, uint bitOffset, uint bitCount)
|
||||
{
|
||||
var uintValue = ReadMultipleBitsAsUInt(ptr, bitOffset, bitCount);
|
||||
var maxValue = (uint)((1UL << (int)bitCount) - 1);
|
||||
return NumberHelpers.UIntToNormalizedFloat(uintValue, 0, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes bits memory region as normalized unsigned integer, up to and including 32 bits, least-significant bit first (LSB).
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to memory region.</param>
|
||||
/// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param>
|
||||
/// <param name="bitCount">Number of bits to read.</param>
|
||||
/// <param name="value">Normalized value to write.</param>
|
||||
public static void WriteNormalizedUIntAsMultipleBits(void* ptr, uint bitOffset, uint bitCount, float value)
|
||||
{
|
||||
var maxValue = (uint)((1UL << (int)bitCount) - 1);
|
||||
var uintValue = NumberHelpers.NormalizedFloatToUInt(value, 0, maxValue);
|
||||
WriteUIntAsMultipleBits(ptr, bitOffset, bitCount, uintValue);
|
||||
}
|
||||
|
||||
public static void SetBitsInBuffer(void* buffer, int byteOffset, int bitOffset, int sizeInBits, bool value)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentException("A buffer must be provided to apply the bitmask on", nameof(buffer));
|
||||
if (sizeInBits < 0)
|
||||
throw new ArgumentException("Negative sizeInBits", nameof(sizeInBits));
|
||||
if (bitOffset < 0)
|
||||
throw new ArgumentException("Negative bitOffset", nameof(bitOffset));
|
||||
if (byteOffset < 0)
|
||||
throw new ArgumentException("Negative byteOffset", nameof(byteOffset));
|
||||
|
||||
// If we're offset by more than a byte, adjust our pointers.
|
||||
if (bitOffset >= 8)
|
||||
{
|
||||
var skipBytes = bitOffset / 8;
|
||||
byteOffset += skipBytes;
|
||||
bitOffset %= 8;
|
||||
}
|
||||
|
||||
var bytePos = (byte*)buffer + byteOffset;
|
||||
var sizeRemainingInBits = sizeInBits;
|
||||
|
||||
// Handle first byte separately if unaligned to byte boundary.
|
||||
if (bitOffset != 0)
|
||||
{
|
||||
var mask = 0xFF << bitOffset;
|
||||
if (sizeRemainingInBits + bitOffset < 8)
|
||||
{
|
||||
mask &= 0xFF >> (8 - (sizeRemainingInBits + bitOffset));
|
||||
}
|
||||
|
||||
if (value)
|
||||
*bytePos |= (byte)mask;
|
||||
else
|
||||
*bytePos &= (byte)~mask;
|
||||
++bytePos;
|
||||
sizeRemainingInBits -= 8 - bitOffset;
|
||||
}
|
||||
|
||||
// Handle full bytes in-between.
|
||||
while (sizeRemainingInBits >= 8)
|
||||
{
|
||||
*bytePos = value ? (byte)0xFF : (byte)0;
|
||||
++bytePos;
|
||||
sizeRemainingInBits -= 8;
|
||||
}
|
||||
|
||||
// Handle unaligned trailing byte, if present.
|
||||
if (sizeRemainingInBits > 0)
|
||||
{
|
||||
var mask = (byte)(0xFF >> 8 - sizeRemainingInBits);
|
||||
if (value)
|
||||
*bytePos |= mask;
|
||||
else
|
||||
*bytePos &= (byte)~mask;
|
||||
}
|
||||
|
||||
Debug.Assert(bytePos <= (byte*)buffer +
|
||||
ComputeFollowingByteOffset((uint)byteOffset, (uint)bitOffset + (uint)sizeInBits));
|
||||
}
|
||||
|
||||
public static void Swap<TValue>(ref TValue a, ref TValue b)
|
||||
{
|
||||
var temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
|
||||
public static uint AlignNatural(uint offset, uint sizeInBytes)
|
||||
{
|
||||
var alignment = Math.Min(8, sizeInBytes);
|
||||
return offset.AlignToMultipleOf(alignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 760fac1b814a4d99a4fc534934e4dc99
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class MiscHelpers
|
||||
{
|
||||
public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
|
||||
{
|
||||
return dictionary.TryGetValue(key, out var value) ? value : default;
|
||||
}
|
||||
|
||||
public static IEnumerable<TValue> EveryNth<TValue>(this IEnumerable<TValue> enumerable, int n, int start = 0)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var element in enumerable)
|
||||
{
|
||||
if (index < start)
|
||||
{
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((index - start) % n == 0)
|
||||
yield return element;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
public static int IndexOf<TValue>(this IEnumerable<TValue> enumerable, TValue value)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var element in enumerable)
|
||||
if (EqualityComparer<TValue>.Default.Equals(element, value))
|
||||
return index;
|
||||
else
|
||||
++index;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d9d9634637c440896a123c44c9e262b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
////REVIEW: why is this public?
|
||||
|
||||
////TODO: add array support
|
||||
|
||||
////TODO: switch parsing to use to Substring
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A combination of a name and an optional list of named parameter values. For example, "Clamp(min=1,max=2)".
|
||||
/// </summary>
|
||||
public struct NameAndParameters
|
||||
{
|
||||
public string name { get; set; }
|
||||
public ReadOnlyArray<NamedValue> parameters { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (parameters.Count == 0)
|
||||
return name;
|
||||
var parameterString = string.Join(NamedValue.Separator, parameters.Select(x => x.ToString()).ToArray());
|
||||
return $"{name}({parameterString})";
|
||||
}
|
||||
|
||||
internal static string ToSerializableString(IEnumerable<NameAndParameters> list)
|
||||
{
|
||||
if (list == null)
|
||||
return string.Empty;
|
||||
|
||||
return string.Join(NamedValue.Separator, list.Select(x => x.ToString()).ToArray());
|
||||
}
|
||||
|
||||
internal static NameAndParameters Create(string name, IList<NamedValue> parameters)
|
||||
{
|
||||
return new NameAndParameters
|
||||
{
|
||||
name = name,
|
||||
parameters = new ReadOnlyArray<NamedValue>(parameters.ToArray())
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<NameAndParameters> ParseMultiple(string text)
|
||||
{
|
||||
List<NameAndParameters> list = null;
|
||||
if (!ParseMultiple(text, ref list))
|
||||
return Enumerable.Empty<NameAndParameters>();
|
||||
return list;
|
||||
}
|
||||
|
||||
internal static bool ParseMultiple(string text, ref List<NameAndParameters> list)
|
||||
{
|
||||
text = text.Trim();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return false;
|
||||
|
||||
if (list == null)
|
||||
list = new List<NameAndParameters>();
|
||||
else
|
||||
list.Clear();
|
||||
|
||||
var index = 0;
|
||||
var textLength = text.Length;
|
||||
|
||||
while (index < textLength)
|
||||
list.Add(ParseNameAndParameters(text, ref index));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static string ParseName(string text)
|
||||
{
|
||||
if (text == null)
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
var index = 0;
|
||||
return ParseNameAndParameters(text, ref index, true).name;
|
||||
}
|
||||
|
||||
public static NameAndParameters Parse(string text)
|
||||
{
|
||||
if (text == null)
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
var index = 0;
|
||||
return ParseNameAndParameters(text, ref index);
|
||||
}
|
||||
|
||||
private static NameAndParameters ParseNameAndParameters(string text, ref int index, bool nameOnly = false)
|
||||
{
|
||||
var textLength = text.Length;
|
||||
|
||||
// Skip whitespace.
|
||||
while (index < textLength && char.IsWhiteSpace(text[index]))
|
||||
++index;
|
||||
|
||||
// Parse name.
|
||||
var nameStart = index;
|
||||
while (index < textLength)
|
||||
{
|
||||
var nextChar = text[index];
|
||||
if (nextChar == '(' || nextChar == NamedValue.Separator[0] || char.IsWhiteSpace(nextChar))
|
||||
break;
|
||||
++index;
|
||||
}
|
||||
if (index - nameStart == 0)
|
||||
throw new ArgumentException($"Expecting name at position {nameStart} in '{text}'", nameof(text));
|
||||
var name = text.Substring(nameStart, index - nameStart);
|
||||
if (nameOnly)
|
||||
return new NameAndParameters {name = name};
|
||||
|
||||
// Skip whitespace.
|
||||
while (index < textLength && char.IsWhiteSpace(text[index]))
|
||||
++index;
|
||||
|
||||
// Parse parameters.
|
||||
NamedValue[] parameters = null;
|
||||
if (index < textLength && text[index] == '(')
|
||||
{
|
||||
++index;
|
||||
var closeParenIndex = text.IndexOf(')', index);
|
||||
if (closeParenIndex == -1)
|
||||
throw new ArgumentException($"Expecting ')' after '(' at position {index} in '{text}'", nameof(text));
|
||||
|
||||
var parameterString = text.Substring(index, closeParenIndex - index);
|
||||
parameters = NamedValue.ParseMultiple(parameterString);
|
||||
index = closeParenIndex + 1;
|
||||
}
|
||||
|
||||
if (index < textLength && (text[index] == ',' || text[index] == InputBinding.Separator))
|
||||
++index;
|
||||
|
||||
return new NameAndParameters {name = name, parameters = new ReadOnlyArray<NamedValue>(parameters)};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ffbcc8a975d04f12967a085a10aeb7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A combination of a name and a value assignment for it.
|
||||
/// </summary>
|
||||
public struct NamedValue : IEquatable<NamedValue>
|
||||
{
|
||||
public const string Separator = ",";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the parameter.
|
||||
/// </summary>
|
||||
public string name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value of the parameter.
|
||||
/// </summary>
|
||||
public PrimitiveValue value { get; set; }
|
||||
|
||||
public TypeCode type => value.type;
|
||||
|
||||
public NamedValue ConvertTo(TypeCode type)
|
||||
{
|
||||
return new NamedValue
|
||||
{
|
||||
name = name,
|
||||
value = value.ConvertTo(type)
|
||||
};
|
||||
}
|
||||
|
||||
public static NamedValue From<TValue>(string name, TValue value)
|
||||
where TValue : struct
|
||||
{
|
||||
return new NamedValue
|
||||
{
|
||||
name = name,
|
||||
value = PrimitiveValue.From(value)
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{name}={value}";
|
||||
}
|
||||
|
||||
public bool Equals(NamedValue other)
|
||||
{
|
||||
return string.Equals(name, other.name, StringComparison.InvariantCultureIgnoreCase)
|
||||
&& value == other.value;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
return obj is NamedValue parameterValue && Equals(parameterValue);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (name != null ? name.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ value.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator==(NamedValue left, NamedValue right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator!=(NamedValue left, NamedValue right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public static NamedValue[] ParseMultiple(string parameterString)
|
||||
{
|
||||
if (parameterString == null)
|
||||
throw new ArgumentNullException(nameof(parameterString));
|
||||
|
||||
parameterString = parameterString.Trim();
|
||||
if (string.IsNullOrEmpty(parameterString))
|
||||
return null;
|
||||
|
||||
var parameterCount = parameterString.CountOccurrences(Separator[0]) + 1;
|
||||
var parameters = new NamedValue[parameterCount];
|
||||
|
||||
var index = 0;
|
||||
for (var i = 0; i < parameterCount; ++i)
|
||||
{
|
||||
var parameter = ParseParameter(parameterString, ref index);
|
||||
parameters[i] = parameter;
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public static NamedValue Parse(string str)
|
||||
{
|
||||
var index = 0;
|
||||
return ParseParameter(str, ref index);
|
||||
}
|
||||
|
||||
private static NamedValue ParseParameter(string parameterString, ref int index)
|
||||
{
|
||||
var parameter = new NamedValue();
|
||||
var parameterStringLength = parameterString.Length;
|
||||
|
||||
// Skip whitespace.
|
||||
while (index < parameterStringLength && char.IsWhiteSpace(parameterString[index]))
|
||||
++index;
|
||||
|
||||
// Parse name.
|
||||
var nameStart = index;
|
||||
while (index < parameterStringLength)
|
||||
{
|
||||
var nextChar = parameterString[index];
|
||||
if (nextChar == '=' || nextChar == Separator[0] || char.IsWhiteSpace(nextChar))
|
||||
break;
|
||||
++index;
|
||||
}
|
||||
parameter.name = parameterString.Substring(nameStart, index - nameStart);
|
||||
|
||||
// Skip whitespace.
|
||||
while (index < parameterStringLength && char.IsWhiteSpace(parameterString[index]))
|
||||
++index;
|
||||
|
||||
if (index == parameterStringLength || parameterString[index] != '=')
|
||||
{
|
||||
// No value given so take "=true" as implied.
|
||||
parameter.value = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
++index; // Skip over '='.
|
||||
|
||||
// Skip whitespace.
|
||||
while (index < parameterStringLength && char.IsWhiteSpace(parameterString[index]))
|
||||
++index;
|
||||
|
||||
// Parse value.
|
||||
var valueStart = index;
|
||||
while (index < parameterStringLength &&
|
||||
!(parameterString[index] == Separator[0] || char.IsWhiteSpace(parameterString[index])))
|
||||
++index;
|
||||
|
||||
////TODO: use Substring struct here so that we don't allocate lots of useless strings
|
||||
|
||||
var value = parameterString.Substring(valueStart, index - valueStart);
|
||||
parameter.value = PrimitiveValue.FromString(value);
|
||||
}
|
||||
|
||||
if (index < parameterStringLength && parameterString[index] == Separator[0])
|
||||
++index;
|
||||
|
||||
return parameter;
|
||||
}
|
||||
|
||||
public void ApplyToObject(object instance)
|
||||
{
|
||||
if (instance == null)
|
||||
throw new System.ArgumentNullException(nameof(instance));
|
||||
|
||||
var instanceType = instance.GetType();
|
||||
|
||||
////REVIEW: what about properties?
|
||||
var field = instanceType.GetField(name,
|
||||
BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (field == null)
|
||||
throw new ArgumentException(
|
||||
$"Cannot find public field '{name}' in '{instanceType.Name}' (while trying to apply parameter)", nameof(instance));
|
||||
|
||||
////REVIEW: would be awesome to be able to do this without boxing
|
||||
var fieldTypeCode = Type.GetTypeCode(field.FieldType);
|
||||
field.SetValue(instance, value.ConvertTo(fieldTypeCode).ToObject());
|
||||
}
|
||||
|
||||
public static void ApplyAllToObject<TParameterList>(object instance, TParameterList parameters)
|
||||
where TParameterList : IEnumerable<NamedValue>
|
||||
{
|
||||
foreach (var parameter in parameters)
|
||||
parameter.ApplyToObject(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e0c005ff24ec4cfb951eefa16239b9b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class NumberHelpers
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int AlignToMultipleOf(this int number, int alignment)
|
||||
{
|
||||
var remainder = number % alignment;
|
||||
if (remainder == 0)
|
||||
return number;
|
||||
|
||||
return number + alignment - remainder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static long AlignToMultipleOf(this long number, long alignment)
|
||||
{
|
||||
var remainder = number % alignment;
|
||||
if (remainder == 0)
|
||||
return number;
|
||||
|
||||
return number + alignment - remainder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint AlignToMultipleOf(this uint number, uint alignment)
|
||||
{
|
||||
var remainder = number % alignment;
|
||||
if (remainder == 0)
|
||||
return number;
|
||||
|
||||
return number + alignment - remainder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Approximately(double a, double b)
|
||||
{
|
||||
return Math.Abs(b - a) < Math.Max(1E-06 * Math.Max(Math.Abs(a), Math.Abs(b)), double.Epsilon * 8);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float IntToNormalizedFloat(int value, int minValue, int maxValue)
|
||||
{
|
||||
if (value <= minValue)
|
||||
return 0.0f;
|
||||
if (value >= maxValue)
|
||||
return 1.0f;
|
||||
// using double here because int.MaxValue is not representable in floats
|
||||
// as int.MaxValue = 2147483647 will become 2147483648.0 when casted to a float
|
||||
return (float)(((double)value - minValue) / ((double)maxValue - minValue));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int NormalizedFloatToInt(float value, int intMinValue, int intMaxValue)
|
||||
{
|
||||
if (value <= 0.0f)
|
||||
return intMinValue;
|
||||
if (value >= 1.0f)
|
||||
return intMaxValue;
|
||||
return (int)(value * ((double)intMaxValue - intMinValue) + intMinValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float UIntToNormalizedFloat(uint value, uint minValue, uint maxValue)
|
||||
{
|
||||
if (value <= minValue)
|
||||
return 0.0f;
|
||||
if (value >= maxValue)
|
||||
return 1.0f;
|
||||
// using double here because uint.MaxValue is not representable in floats
|
||||
return (float)(((double)value - minValue) / ((double)maxValue - minValue));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint NormalizedFloatToUInt(float value, uint uintMinValue, uint uintMaxValue)
|
||||
{
|
||||
if (value <= 0.0f)
|
||||
return uintMinValue;
|
||||
if (value >= 1.0f)
|
||||
return uintMaxValue;
|
||||
return (uint)(value * ((double)uintMaxValue - uintMinValue) + uintMinValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint RemapUIntBitsToNormalizeFloatToUIntBits(uint value, uint inBitSize, uint outBitSize)
|
||||
{
|
||||
var inMaxValue = (uint)((1UL << (int)inBitSize) - 1);
|
||||
var outMaxValue = (uint)((1UL << (int)outBitSize) - 1);
|
||||
|
||||
var normFloat = UIntToNormalizedFloat(value, 0, inMaxValue);
|
||||
return NormalizedFloatToUInt(normFloat, 0, outMaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15eaf34440a14edf94d5a0a00cbded20
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f8944bd1ba848ab8f95afe7acfb4b79
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal class ForDeviceEventObservable : IObservable<InputEventPtr>
|
||||
{
|
||||
private IObservable<InputEventPtr> m_Source;
|
||||
private InputDevice m_Device;
|
||||
private Type m_DeviceType;
|
||||
|
||||
public ForDeviceEventObservable(IObservable<InputEventPtr> source, Type deviceType, InputDevice device)
|
||||
{
|
||||
m_Source = source;
|
||||
m_DeviceType = deviceType;
|
||||
m_Device = device;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<InputEventPtr> observer)
|
||||
{
|
||||
return m_Source.Subscribe(new ForDevice(m_DeviceType, m_Device, observer));
|
||||
}
|
||||
|
||||
private class ForDevice : IObserver<InputEventPtr>
|
||||
{
|
||||
private IObserver<InputEventPtr> m_Observer;
|
||||
private InputDevice m_Device;
|
||||
private Type m_DeviceType;
|
||||
|
||||
public ForDevice(Type deviceType, InputDevice device, IObserver<InputEventPtr> observer)
|
||||
{
|
||||
m_Device = device;
|
||||
m_DeviceType = deviceType;
|
||||
m_Observer = observer;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
Debug.LogException(error);
|
||||
}
|
||||
|
||||
public void OnNext(InputEventPtr value)
|
||||
{
|
||||
if (m_DeviceType != null)
|
||||
{
|
||||
var device = InputSystem.GetDeviceById(value.deviceId);
|
||||
if (device == null)
|
||||
return;
|
||||
|
||||
if (!m_DeviceType.IsInstanceOfType(device))
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Device != null && value.deviceId != m_Device.deviceId)
|
||||
return;
|
||||
|
||||
m_Observer.OnNext(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8242c97e8771412fb5f5637b7278e18f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for working with <a ref="https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1">IObservable</a>
|
||||
/// in the context of the Input System.
|
||||
/// </summary>
|
||||
public static class Observable
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter a stream of observable values by a predicate.
|
||||
/// </summary>
|
||||
/// <param name="source">The stream of observable values.</param>
|
||||
/// <param name="predicate">Filter to apply to the stream. Only values for which the predicate returns true
|
||||
/// are passed on to <c>OnNext</c> of the observer.</param>
|
||||
/// <typeparam name="TValue">Value type for the observable stream.</typeparam>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="predicate"/> is <c>null</c>.</exception>
|
||||
/// <returns>A new observable that is filtered by the given predicate.</returns>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputSystem.onEvent
|
||||
/// .Where(e => e.HasButtonPress())
|
||||
/// .Call(e => Debug.Log("Press"));
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputEventListener"/>
|
||||
/// <seealso cref="InputSystem.onEvent"/>
|
||||
public static IObservable<TValue> Where<TValue>(this IObservable<TValue> source, Func<TValue, bool> predicate)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException(nameof(predicate));
|
||||
return new WhereObservable<TValue>(source, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform each value in an observable stream of values into a value of a different type.
|
||||
/// </summary>
|
||||
/// <param name="source">The stream of observable values.</param>
|
||||
/// <param name="filter">Function to transform values in the stream.</param>
|
||||
/// <typeparam name="TSource">Type of source values to transform from.</typeparam>
|
||||
/// <typeparam name="TResult">Type of target values to transform to.</typeparam>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="filter"/> is <c>null</c>.</exception>
|
||||
/// <returns>A new observable of values of the new result type.</returns>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputSystem.onEvent
|
||||
/// .Select(eventPtr => eventPtr.GetFirstButtonPressOrNull())
|
||||
/// .Call(ctrl =>
|
||||
/// {
|
||||
/// if (ctrl != null)
|
||||
/// Debug.Log(ctrl);
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputEventListener"/>
|
||||
/// <seealso cref="InputSystem.onEvent"/>
|
||||
public static IObservable<TResult> Select<TSource, TResult>(this IObservable<TSource> source, Func<TSource, TResult> filter)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (filter == null)
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
return new SelectObservable<TSource, TResult>(source, filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform each value in an observable stream of values such that one value is translated to zero or more values
|
||||
/// of a new type.
|
||||
/// </summary>
|
||||
/// <param name="source">The stream of observable values.</param>
|
||||
/// <param name="filter">Function to transform each value in the stream into zero or more new values.</param>
|
||||
/// <typeparam name="TSource">Type of source values to transform from.</typeparam>
|
||||
/// <typeparam name="TResult">Type of target values to transform to.</typeparam>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="filter"/> is <c>null</c>.</exception>
|
||||
/// <returns>A new observable of values of the new result type.</returns>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputSystem.onEvent
|
||||
/// .SelectMany(eventPtr => eventPtr.GetAllButtonPresses())
|
||||
/// .Call(ctrl =>
|
||||
/// Debug.Log($"Button {ctrl} pressed"));
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputEventListener"/>
|
||||
/// <seealso cref="InputSystem.onEvent"/>
|
||||
public static IObservable<TResult> SelectMany<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IEnumerable<TResult>> filter)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (filter == null)
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
return new SelectManyObservable<TSource, TResult>(source, filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take up to the first N values from the given observable stream of values.
|
||||
/// </summary>
|
||||
/// <param name="source">An observable source of values.</param>
|
||||
/// <param name="count">The maximum number of values to take from the source.</param>
|
||||
/// <typeparam name="TValue">Types of values to read from the stream.</typeparam>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative.</exception>
|
||||
/// <returns>A stream of up to <paramref name="count"/> values.</returns>
|
||||
public static IObservable<TValue> Take<TValue>(this IObservable<TValue> source, int count)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
return new TakeNObservable<TValue>(source, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From an observable stream of events, take only those that are for the given <paramref name="device"/>.
|
||||
/// </summary>
|
||||
/// <param name="source">An observable stream of events.</param>
|
||||
/// <param name="device">Device to filter events for.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c>.</exception>
|
||||
/// <returns>An observable stream of events for the given device.</returns>
|
||||
/// <remarks>
|
||||
/// Each event has an <see cref="InputEvent.deviceId"/> associated with it. This is used to match
|
||||
/// against the <see cref="InputDevice.deviceId"/> of <paramref name="device"/>.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputSystem.onEvent
|
||||
/// .ForDevice(Mouse.current)
|
||||
/// .Call(e => Debug.Log($"Mouse event: {e}");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputEvent.deviceId"/>
|
||||
/// <seealso cref="InputEventListener"/>
|
||||
/// <seealso cref="InputSystem.onEvent"/>
|
||||
public static IObservable<InputEventPtr> ForDevice(this IObservable<InputEventPtr> source, InputDevice device)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
return new ForDeviceEventObservable(source, null, device);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From an observable stream of events, take only those that are for a device of the given type.
|
||||
/// </summary>
|
||||
/// <param name="source">An observable stream of events.</param>
|
||||
/// <typeparam name="TDevice">Type of device (such as <see cref="Gamepad"/>) to filter for.</typeparam>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c>.</exception>
|
||||
/// <returns>An observable stream of events for devices of type <typeparamref name="TDevice"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputSystem.onEvent
|
||||
/// .ForDevice<Gamepad>()
|
||||
/// .Where(e => e.HasButtonPress())
|
||||
/// .CallOnce(e => PlayerInput.Instantiate(myPrefab,
|
||||
/// pairWithDevice: InputSystem.GetDeviceById(e.deviceId)));
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputEventListener"/>
|
||||
/// <seealso cref="InputSystem.onEvent"/>
|
||||
public static IObservable<InputEventPtr> ForDevice<TDevice>(this IObservable<InputEventPtr> source)
|
||||
where TDevice : InputDevice
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
return new ForDeviceEventObservable(source, typeof(TDevice), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call an action for the first value in the given stream of values and then automatically dispose
|
||||
/// the observer.
|
||||
/// </summary>
|
||||
/// <param name="source">An observable source of values.</param>
|
||||
/// <param name="action">Action to call for the first value that arrives from the source.</param>
|
||||
/// <typeparam name="TValue">Type of values delivered by the source.</typeparam>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="action"/> is <c>null</c>.</exception>
|
||||
/// <returns>A handle to the subscription. Call <c>Dispose</c> to unsubscribe at any time.</returns>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputSystem.onEvent
|
||||
/// .Where(e => e.type == DeviceConfigurationEvent.typeStatic)
|
||||
/// .CallOnce(_ => Debug.Log("Device configuration changed"));
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputEventListener"/>
|
||||
/// <seealso cref="InputSystem.onEvent"/>
|
||||
/// <seealso cref="Call{TValue}"/>
|
||||
public static IDisposable CallOnce<TValue>(this IObservable<TValue> source, Action<TValue> action)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
IDisposable subscription = null;
|
||||
subscription = source.Take(1).Subscribe(new Observer<TValue>(action, () => subscription?.Dispose()));
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call the given callback for every value generated by the given observable stream of values.
|
||||
/// </summary>
|
||||
/// <param name="source">An observable stream of values.</param>
|
||||
/// <param name="action">A callback to invoke for each value.</param>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <c>null</c> -or- <paramref name="action"/> is <c>null</c>.</exception>
|
||||
/// <returns>A handle to the subscription. Call <c>Dispose</c> to unsubscribe at any time.</returns>
|
||||
/// <remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InputSystem.onEvent
|
||||
/// .Where(e => e.type == DeviceConfigurationEvent.typeStatic)
|
||||
/// .Call(_ => Debug.Log("Device configuration changed"));
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputEventListener"/>
|
||||
/// <seealso cref="InputSystem.onEvent"/>
|
||||
/// <seealso cref="CallOnce{TValue}"/>
|
||||
public static IDisposable Call<TValue>(this IObservable<TValue> source, Action<TValue> action)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
return source.Subscribe(new Observer<TValue>(action));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9586270a56c4658a83ff649811e0cb3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal class Observer<TValue> : IObserver<TValue>
|
||||
{
|
||||
private Action<TValue> m_OnNext;
|
||||
private Action m_OnCompleted;
|
||||
|
||||
public Observer(Action<TValue> onNext, Action onCompleted = null)
|
||||
{
|
||||
m_OnNext = onNext;
|
||||
m_OnCompleted = onCompleted;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
m_OnCompleted?.Invoke();
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
Debug.LogException(error);
|
||||
}
|
||||
|
||||
public void OnNext(TValue evt)
|
||||
{
|
||||
m_OnNext?.Invoke(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92b3f5ed3a3e41daaf0bed64ebc854d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal class SelectManyObservable<TSource, TResult> : IObservable<TResult>
|
||||
{
|
||||
private readonly IObservable<TSource> m_Source;
|
||||
private readonly Func<TSource, IEnumerable<TResult>> m_Filter;
|
||||
|
||||
public SelectManyObservable(IObservable<TSource> source, Func<TSource, IEnumerable<TResult>> filter)
|
||||
{
|
||||
m_Source = source;
|
||||
m_Filter = filter;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TResult> observer)
|
||||
{
|
||||
return m_Source.Subscribe(new Select(this, observer));
|
||||
}
|
||||
|
||||
private class Select : IObserver<TSource>
|
||||
{
|
||||
private SelectManyObservable<TSource, TResult> m_Observable;
|
||||
private readonly IObserver<TResult> m_Observer;
|
||||
|
||||
public Select(SelectManyObservable<TSource, TResult> observable, IObserver<TResult> observer)
|
||||
{
|
||||
m_Observable = observable;
|
||||
m_Observer = observer;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
Debug.LogException(error);
|
||||
}
|
||||
|
||||
public void OnNext(TSource evt)
|
||||
{
|
||||
foreach (var result in m_Observable.m_Filter(evt))
|
||||
m_Observer.OnNext(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e12c30fe9774947a81ce7eb7054cfb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.LowLevel
|
||||
{
|
||||
internal class SelectObservable<TSource, TResult> : IObservable<TResult>
|
||||
{
|
||||
private readonly IObservable<TSource> m_Source;
|
||||
private readonly Func<TSource, TResult> m_Filter;
|
||||
|
||||
public SelectObservable(IObservable<TSource> source, Func<TSource, TResult> filter)
|
||||
{
|
||||
m_Source = source;
|
||||
m_Filter = filter;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TResult> observer)
|
||||
{
|
||||
return m_Source.Subscribe(new Select(this, observer));
|
||||
}
|
||||
|
||||
private class Select : IObserver<TSource>
|
||||
{
|
||||
private SelectObservable<TSource, TResult> m_Observable;
|
||||
private readonly IObserver<TResult> m_Observer;
|
||||
|
||||
public Select(SelectObservable<TSource, TResult> observable, IObserver<TResult> observer)
|
||||
{
|
||||
m_Observable = observable;
|
||||
m_Observer = observer;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
Debug.LogException(error);
|
||||
}
|
||||
|
||||
public void OnNext(TSource evt)
|
||||
{
|
||||
var result = m_Observable.m_Filter(evt);
|
||||
m_Observer.OnNext(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29a78370a46c4b59875764266185b105
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal class TakeNObservable<TValue> : IObservable<TValue>
|
||||
{
|
||||
private IObservable<TValue> m_Source;
|
||||
private int m_Count;
|
||||
|
||||
public TakeNObservable(IObservable<TValue> source, int count)
|
||||
{
|
||||
m_Source = source;
|
||||
m_Count = count;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TValue> observer)
|
||||
{
|
||||
return m_Source.Subscribe(new Take(this, observer));
|
||||
}
|
||||
|
||||
private class Take : IObserver<TValue>
|
||||
{
|
||||
private IObserver<TValue> m_Observer;
|
||||
private int m_Remaining;
|
||||
|
||||
public Take(TakeNObservable<TValue> observable, IObserver<TValue> observer)
|
||||
{
|
||||
m_Observer = observer;
|
||||
m_Remaining = observable.m_Count;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
Debug.LogException(error);
|
||||
}
|
||||
|
||||
public void OnNext(TValue evt)
|
||||
{
|
||||
if (m_Remaining <= 0)
|
||||
return;
|
||||
|
||||
m_Remaining--;
|
||||
m_Observer.OnNext(evt);
|
||||
|
||||
if (m_Remaining == 0)
|
||||
{
|
||||
m_Observer.OnCompleted();
|
||||
m_Observer = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25881642ba784356b23ef34898fdc690
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal class WhereObservable<TValue> : IObservable<TValue>
|
||||
{
|
||||
private readonly IObservable<TValue> m_Source;
|
||||
private readonly Func<TValue, bool> m_Predicate;
|
||||
|
||||
public WhereObservable(IObservable<TValue> source, Func<TValue, bool> predicate)
|
||||
{
|
||||
m_Source = source;
|
||||
m_Predicate = predicate;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TValue> observer)
|
||||
{
|
||||
return m_Source.Subscribe(new Where(this, observer));
|
||||
}
|
||||
|
||||
private class Where : IObserver<TValue>
|
||||
{
|
||||
private WhereObservable<TValue> m_Observable;
|
||||
private readonly IObserver<TValue> m_Observer;
|
||||
|
||||
public Where(WhereObservable<TValue> observable, IObserver<TValue> observer)
|
||||
{
|
||||
m_Observable = observable;
|
||||
m_Observer = observer;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
Debug.LogException(error);
|
||||
}
|
||||
|
||||
public void OnNext(TValue evt)
|
||||
{
|
||||
if (m_Observable.m_Predicate(evt))
|
||||
m_Observer.OnNext(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea70775d141141a3bca036e60a89e8fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper when having either a single element or a list of elements. Avoids
|
||||
/// having to allocate GC heap garbage or having to alternatively split code paths.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
internal struct OneOrMore<TValue, TList> : IReadOnlyList<TValue>
|
||||
where TList : IReadOnlyList<TValue>
|
||||
{
|
||||
private readonly bool m_IsSingle;
|
||||
private readonly TValue m_Single;
|
||||
private readonly TList m_Multiple;
|
||||
|
||||
public int Count => m_IsSingle ? 1 : m_Multiple.Count;
|
||||
|
||||
public TValue this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_IsSingle)
|
||||
return m_Multiple[index];
|
||||
|
||||
if (index < 0 || index > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return m_Single;
|
||||
}
|
||||
}
|
||||
|
||||
public OneOrMore(TValue single)
|
||||
{
|
||||
m_IsSingle = true;
|
||||
m_Single = single;
|
||||
m_Multiple = default;
|
||||
}
|
||||
|
||||
public OneOrMore(TList multiple)
|
||||
{
|
||||
m_IsSingle = false;
|
||||
m_Single = default;
|
||||
m_Multiple = multiple;
|
||||
}
|
||||
|
||||
public static implicit operator OneOrMore<TValue, TList>(TValue single)
|
||||
{
|
||||
return new OneOrMore<TValue, TList>(single);
|
||||
}
|
||||
|
||||
public static implicit operator OneOrMore<TValue, TList>(TList multiple)
|
||||
{
|
||||
return new OneOrMore<TValue, TList>(multiple);
|
||||
}
|
||||
|
||||
public IEnumerator<TValue> GetEnumerator()
|
||||
{
|
||||
return new Enumerator { m_List = this };
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
private class Enumerator : IEnumerator<TValue>
|
||||
{
|
||||
internal int m_Index = -1;
|
||||
internal OneOrMore<TValue, TList> m_List;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
++m_Index;
|
||||
if (m_Index >= m_List.Count)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_Index = -1;
|
||||
}
|
||||
|
||||
public TValue Current => m_List[m_Index];
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f99eea29fb7e4f7a9c7aad1730f329a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal struct PredictiveParser
|
||||
{
|
||||
public void ExpectSingleChar(ReadOnlySpan<char> str, char c)
|
||||
{
|
||||
if (str[m_Position] != c)
|
||||
throw new InvalidOperationException($"Expected a '{c}' character at position {m_Position} in : {str.ToString()}");
|
||||
|
||||
++m_Position;
|
||||
}
|
||||
|
||||
public int ExpectInt(ReadOnlySpan<char> str)
|
||||
{
|
||||
int pos = m_Position;
|
||||
|
||||
int sign = 1;
|
||||
if (str[pos] == '-')
|
||||
{
|
||||
sign = -1;
|
||||
++pos;
|
||||
}
|
||||
|
||||
int value = 0;
|
||||
for (;;)
|
||||
{
|
||||
var n = str[pos];
|
||||
if (n >= '0' && n <= '9')
|
||||
{
|
||||
value *= 10;
|
||||
value += n - '0';
|
||||
++pos;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_Position == pos)
|
||||
throw new InvalidOperationException($"Expected an int at position {m_Position} in {str.ToString()}");
|
||||
|
||||
m_Position = pos;
|
||||
return value * sign;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> ExpectString(ReadOnlySpan<char> str)
|
||||
{
|
||||
var startPos = m_Position;
|
||||
if (str[startPos] != '\"')
|
||||
throw new InvalidOperationException($"Expected a '\"' character at position {m_Position} in {str.ToString()}");
|
||||
|
||||
++m_Position;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
var c = str[m_Position];
|
||||
c |= ' ';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
{
|
||||
++m_Position;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// if the first non-alpha character is not a quote, throw
|
||||
if (str[m_Position] != '\"')
|
||||
throw new InvalidOperationException($"Expected a closing '\"' character at position {m_Position} in string: {str.ToString()}");
|
||||
|
||||
if (m_Position - startPos == 1)
|
||||
return ReadOnlySpan<char>.Empty;
|
||||
|
||||
var output = str.Slice(startPos + 1, m_Position - startPos - 1);
|
||||
|
||||
++m_Position;
|
||||
return output;
|
||||
}
|
||||
|
||||
public bool AcceptSingleChar(ReadOnlySpan<char> str, char c)
|
||||
{
|
||||
if (str[m_Position] != c)
|
||||
return false;
|
||||
|
||||
m_Position++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AcceptString(ReadOnlySpan<char> input, out ReadOnlySpan<char> output)
|
||||
{
|
||||
output = default;
|
||||
var startPos = m_Position;
|
||||
var endPos = startPos;
|
||||
if (input[endPos] != '\"')
|
||||
return false;
|
||||
|
||||
++endPos;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
var c = input[endPos];
|
||||
c |= ' ';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
{
|
||||
++endPos;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// if the first non-alpha character is not a quote, throw
|
||||
if (input[endPos] != '\"')
|
||||
return false;
|
||||
|
||||
// empty string?
|
||||
if (m_Position - startPos == 1)
|
||||
output = ReadOnlySpan<char>.Empty;
|
||||
else
|
||||
output = input.Slice(startPos + 1, endPos - startPos - 1);
|
||||
|
||||
m_Position = endPos + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AcceptInt(ReadOnlySpan<char> str)
|
||||
{
|
||||
// skip negative sign
|
||||
if (str[m_Position] == '-')
|
||||
++m_Position;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
var n = str[m_Position];
|
||||
if (n >= '0' && n <= '9')
|
||||
++m_Position;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int m_Position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e4e169a1433dc94cb99eff69a1c6be8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 455d9bc907fb62d44a2195e53ff7c0b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
////REVIEW: switch to something that doesn't require the backing store to be an actual array?
|
||||
//// (maybe switch m_Array to an InlinedArray and extend InlinedArray to allow having three configs:
|
||||
//// 1. firstValue only, 2. firstValue + additionalValues, 3. everything in additionalValues)
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-only access to an array or to a slice of an array.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Type of values stored in the array.</typeparam>
|
||||
/// <remarks>
|
||||
/// The purpose of this struct is to allow exposing internal arrays directly such that no
|
||||
/// boxing and no going through interfaces is required but at the same time not allowing
|
||||
/// the internal arrays to be modified.
|
||||
///
|
||||
/// It differs from <c>ReadOnlySpan<T></c> in that it can be stored on the heap and differs
|
||||
/// from <c>ReadOnlyCollection<T></c> in that it supports slices directly without needing
|
||||
/// an intermediate object representing the slice.
|
||||
///
|
||||
/// Note that in most cases, the ReadOnlyArray instance should be treated as a <em>temporary</em>.
|
||||
/// The actual array referred to by a ReadOnlyArray instance is usually owned and probably mutated
|
||||
/// by another piece of code. When that code makes changes to the array, the ReadOnlyArray
|
||||
/// instance will not get updated.
|
||||
/// </remarks>
|
||||
public struct ReadOnlyArray<TValue> : IReadOnlyList<TValue>
|
||||
{
|
||||
internal TValue[] m_Array;
|
||||
internal int m_StartIndex;
|
||||
internal int m_Length;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a read-only array covering all of the given array.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to index.</param>
|
||||
public ReadOnlyArray(TValue[] array)
|
||||
{
|
||||
m_Array = array;
|
||||
m_StartIndex = 0;
|
||||
m_Length = array?.Length ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a read-only array that covers only the given slice of <paramref name="array"/>.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to index.</param>
|
||||
/// <param name="index">Index at which to start indexing <paramref name="array"/>. The given element
|
||||
/// becomes index #0 for the read-only array.</param>
|
||||
/// <param name="length">Length of the slice to index from <paramref name="array"/>.</param>
|
||||
public ReadOnlyArray(TValue[] array, int index, int length)
|
||||
{
|
||||
m_Array = array;
|
||||
m_StartIndex = index;
|
||||
m_Length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert to array.
|
||||
/// </summary>
|
||||
/// <returns>A new array containing a copy of the contents of the read-only array.</returns>
|
||||
public TValue[] ToArray()
|
||||
{
|
||||
var result = new TValue[m_Length];
|
||||
if (m_Length > 0)
|
||||
Array.Copy(m_Array, m_StartIndex, result, 0, m_Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the first element in the array for which the given <paramref name="predicate"/> is <c>true</c> and returns the index of that element.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate to be evaluated for each element which defines the condition for the search.</param>
|
||||
/// <returns>Index of the first element for which <paramref name="predicate"/> is <c>true</c>x or -1 if no such element exists.</returns>
|
||||
/// <exception cref="ArgumentNullException">If predicate is <c>null</c>.</exception>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Searches for the first element in an integer array that is greater or equal to 5.
|
||||
/// var haystack = new ReadOnlyArray<int>(new[] { 1, 2, 3, 4, 5, 6, 7 });
|
||||
/// var index = haystack.IndexOf((value) => value >= 5); // index == 4
|
||||
/// </code>
|
||||
/// </example>
|
||||
public int IndexOf(Predicate<TValue> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException(nameof(predicate));
|
||||
|
||||
for (var i = 0; i < m_Length; ++i)
|
||||
if (predicate(m_Array[m_StartIndex + i]))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the read-only array.
|
||||
/// <returns>
|
||||
/// <see cref="ReadOnlyArray{TValue}.Enumerator"/>
|
||||
/// An enumerator for the read-only array.
|
||||
/// </returns>
|
||||
/// </summary>
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(m_Array, m_StartIndex, m_Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a read-only array containing elements <paramref name="array"/>.
|
||||
/// </summary>
|
||||
/// <param name="array">An existing array containing elements to be wrapped as a read-only array.</param>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "`ToXXX` message only really makes sense as static, which is not recommended for generic types.")]
|
||||
public static implicit operator ReadOnlyArray<TValue>(TValue[] array)
|
||||
{
|
||||
return new ReadOnlyArray<TValue>(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of elements in the array.
|
||||
/// </summary>
|
||||
public int Count => m_Length;
|
||||
|
||||
/// <summary>
|
||||
/// Return the element at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index into the array.</param>
|
||||
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is less than 0 or greater than <see cref="Count"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public TValue this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= m_Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
// We allow array to be null as we are patching up ReadOnlyArrays in a separate
|
||||
// path in several places.
|
||||
if (m_Array == null)
|
||||
throw new InvalidOperationException();
|
||||
return m_Array[m_StartIndex + index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ReadOnlyArray{TValue}"/>
|
||||
/// Enumerates the elements of a read-only array.
|
||||
/// </summary>
|
||||
public struct Enumerator : IEnumerator<TValue>
|
||||
{
|
||||
private readonly TValue[] m_Array;
|
||||
private readonly int m_IndexStart;
|
||||
private readonly int m_IndexEnd;
|
||||
private int m_Index;
|
||||
|
||||
internal Enumerator(TValue[] array, int index, int length)
|
||||
{
|
||||
m_Array = array;
|
||||
m_IndexStart = index - 1; // First call to MoveNext() moves us to first valid index.
|
||||
m_IndexEnd = index + length;
|
||||
m_Index = m_IndexStart;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (m_Index < m_IndexEnd)
|
||||
++m_Index;
|
||||
return m_Index != m_IndexEnd;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reset()
|
||||
{
|
||||
m_Index = m_IndexStart;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TValue Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Index == m_IndexEnd)
|
||||
throw new InvalidOperationException("Iterated beyond end");
|
||||
return m_Array[m_Index];
|
||||
}
|
||||
}
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods to help with <see cref="ReadOnlyArrayExtensions"/> contents.
|
||||
/// </summary>
|
||||
public static class ReadOnlyArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates whether <paramref name="array"/> contains an element that compares equal to <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The array element type.</typeparam>
|
||||
/// <param name="array">Reference to the read-only array to be searched.</param>
|
||||
/// <param name="value">The value to be searched for in <paramref name="array"/>.</param>
|
||||
/// <returns><c>true</c> if <paramref name="array"/> contains <paramref name="value"/>, else <c>false</c>.</returns>
|
||||
public static bool Contains<TValue>(this ReadOnlyArray<TValue> array, TValue value)
|
||||
where TValue : IComparable<TValue>
|
||||
{
|
||||
for (var i = 0; i < array.m_Length; ++i)
|
||||
if (array.m_Array[array.m_StartIndex + i].CompareTo(value) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates whether <paramref name="array"/> contains a reference to <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The array element type.</typeparam>
|
||||
/// <param name="array">Reference to the read-only array to be searched.</param>
|
||||
/// <param name="value">The reference to be searched for in <paramref name="array"/>.</param>
|
||||
/// <returns><c>true</c> if <paramref name="array"/> contains a reference to <paramref name="value"/>, else <c>false</c>.</returns>
|
||||
public static bool ContainsReference<TValue>(this ReadOnlyArray<TValue> array, TValue value)
|
||||
where TValue : class
|
||||
{
|
||||
return IndexOfReference(array, value) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the index of <paramref name="value"/> in <paramref name="array"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The array element type.</typeparam>
|
||||
/// <param name="array">Reference to the read-only array to be searched.</param>
|
||||
/// <param name="value">The reference to be searched for in <paramref name="array"/>.</param>
|
||||
/// <returns>The zero-based index of element <paramref name="value"/> in <paramref name="array"/> if such a reference could be found and -1 if it do not exist.</returns>
|
||||
public static int IndexOfReference<TValue>(this ReadOnlyArray<TValue> array, TValue value)
|
||||
where TValue : class
|
||||
{
|
||||
for (var i = 0; i < array.m_Length; ++i)
|
||||
if (ReferenceEquals(array.m_Array[array.m_StartIndex + i], value))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates whether whether <paramref name="array1"/> and <paramref name="array2"/> contain the same sequence of references.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The array element type.</typeparam>
|
||||
/// <param name="array1">The first array to be evaluated.</param>
|
||||
/// <param name="array2">The second array to be evaluated.</param>
|
||||
/// <param name="count">The maximum number of elements to be compared.</param>
|
||||
/// <returns><c>true</c> if the <paramref name="count"/> first elements of <paramref name="array1"/> and <paramref name="array2"/> contain the same references, else false.</returns>
|
||||
internal static bool HaveEqualReferences<TValue>(this ReadOnlyArray<TValue> array1, IReadOnlyList<TValue> array2, int count = int.MaxValue)
|
||||
{
|
||||
var length1 = Math.Min(array1.Count, count);
|
||||
var length2 = Math.Min(array2.Count, count);
|
||||
|
||||
if (length1 != length2)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < length1; ++i)
|
||||
if (!ReferenceEquals(array1[i], array2[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07003ea62d4443a09691237b23f17ac6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides type erasure and an abstraction of a saved state that
|
||||
/// will (must) be restored at a later point.
|
||||
/// </summary>
|
||||
internal interface ISavedState
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispose current state, should be invoked before RestoreSavedState()
|
||||
/// to dispose the current state before restoring a saved state.
|
||||
/// </summary>
|
||||
void StaticDisposeCurrentState();
|
||||
|
||||
/// <summary>
|
||||
/// Restore previous state, should be invoked after StaticDisposeCurrentState().
|
||||
/// </summary>
|
||||
void RestoreSavedState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to store and support later restoration of a saved
|
||||
/// state. The state is expected to be a value-type. If the state is not restored
|
||||
/// it must be disposed to not leak resources.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value-type representing the state to be stored.</typeparam>
|
||||
internal sealed class SavedStructState<T> : ISavedState where T : struct
|
||||
{
|
||||
public delegate void TypedRestore(ref T state);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a SavedStructState.
|
||||
/// </summary>
|
||||
/// <param name="state">The value-type state to be saved.</param>
|
||||
/// <param name="restoreAction">The action to be carried out to restore state.</param>
|
||||
/// <param name="staticDisposeCurrentState">The action to be carried out to dispose current state. May be null.</param>
|
||||
internal SavedStructState(ref T state, TypedRestore restoreAction, Action staticDisposeCurrentState = null)
|
||||
{
|
||||
Debug.Assert(restoreAction != null, "Restore action is required");
|
||||
|
||||
m_State = state; // copy
|
||||
m_RestoreAction = restoreAction;
|
||||
m_StaticDisposeCurrentState = staticDisposeCurrentState;
|
||||
}
|
||||
|
||||
public void StaticDisposeCurrentState()
|
||||
{
|
||||
if (m_StaticDisposeCurrentState != null)
|
||||
{
|
||||
m_StaticDisposeCurrentState();
|
||||
m_StaticDisposeCurrentState = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void RestoreSavedState()
|
||||
{
|
||||
Debug.Assert(m_StaticDisposeCurrentState == null,
|
||||
$"Should have been disposed via {nameof(StaticDisposeCurrentState)} before invoking {nameof(RestoreSavedState)}");
|
||||
Debug.Assert(m_RestoreAction != null, $"Only invoke {nameof(RestoreSavedState)} once ");
|
||||
m_RestoreAction(ref m_State);
|
||||
m_RestoreAction = null;
|
||||
}
|
||||
|
||||
private T m_State;
|
||||
private TypedRestore m_RestoreAction;
|
||||
private Action m_StaticDisposeCurrentState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e988a22b82d97847b9e8e3cf14a1526
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class SpriteUtilities
|
||||
{
|
||||
public static unsafe Sprite CreateCircleSprite(int radius, Color32 colour)
|
||||
{
|
||||
// cache the diameter
|
||||
var d = radius * 2;
|
||||
|
||||
var texture = new Texture2D(d, d, DefaultFormat.LDR, TextureCreationFlags.None);
|
||||
var colours = texture.GetRawTextureData<Color32>();
|
||||
var coloursPtr = (Color32*)colours.GetUnsafePtr();
|
||||
UnsafeUtility.MemSet(coloursPtr, 0, colours.Length * UnsafeUtility.SizeOf<Color32>());
|
||||
|
||||
// pack the colour into a ulong so we can write two pixels at a time to the texture data
|
||||
var colorPtr = (uint*)UnsafeUtility.AddressOf(ref colour);
|
||||
var colourAsULong = *(ulong*)colorPtr << 32 | *colorPtr;
|
||||
|
||||
float rSquared = radius * radius;
|
||||
|
||||
// loop over the texture memory one column at a time filling in a line between the two x coordinates
|
||||
// of the circle at each column
|
||||
for (var y = -radius; y < radius; y++)
|
||||
{
|
||||
// for the current column, calculate what the x coordinate of the circle would be
|
||||
// using x^2 + y^2 = r^2, or x^2 = r^2 - y^2. The square root of the value of the
|
||||
// x coordinate will equal half the width of the circle at the current y coordinate
|
||||
var halfWidth = (int)Mathf.Sqrt(rSquared - y * y);
|
||||
|
||||
// position the pointer so it points at the memory where we should start filling in
|
||||
// the current line
|
||||
var ptr = coloursPtr
|
||||
+ (y + radius) * d // the position of the memory at the start of the row at the current y coordinate
|
||||
+ radius - halfWidth; // the position along the row where we should start inserting colours
|
||||
|
||||
// fill in two pixels at a time
|
||||
for (var x = 0; x < halfWidth; x++)
|
||||
{
|
||||
*(ulong*)ptr = colourAsULong;
|
||||
ptr += 2;
|
||||
}
|
||||
}
|
||||
|
||||
texture.Apply();
|
||||
|
||||
var sprite = Sprite.Create(texture, new Rect(0, 0, d, d), new Vector2(radius, radius), 1, 0, SpriteMeshType.FullRect);
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf29acc15e9bb2246965162123659116
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,584 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class StringHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// For every character in <paramref name="str"/> that is contained in <paramref name="chars"/>, replace it
|
||||
/// by the corresponding character in <paramref name="replacements"/> preceded by a backslash.
|
||||
/// </summary>
|
||||
public static string Escape(this string str, string chars = "\n\t\r\\\"", string replacements = "ntr\\\"")
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
// Scan for characters that need escaping. If there's none, just return
|
||||
// string as is.
|
||||
var hasCharacterThatNeedsEscaping = false;
|
||||
foreach (var ch in str)
|
||||
{
|
||||
if (chars.Contains(ch))
|
||||
{
|
||||
hasCharacterThatNeedsEscaping = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasCharacterThatNeedsEscaping)
|
||||
return str;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var ch in str)
|
||||
{
|
||||
var index = chars.IndexOf(ch);
|
||||
if (index == -1)
|
||||
{
|
||||
builder.Append(ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append('\\');
|
||||
builder.Append(replacements[index]);
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static string Unescape(this string str, string chars = "ntr\\\"", string replacements = "\n\t\r\\\"")
|
||||
{
|
||||
if (str == null)
|
||||
return str;
|
||||
|
||||
// If there's no backslashes in the string, there's nothing to unescape.
|
||||
if (!str.Contains('\\'))
|
||||
return str;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
for (var i = 0; i < str.Length; ++i)
|
||||
{
|
||||
var ch = str[i];
|
||||
if (ch == '\\' && i < str.Length - 2)
|
||||
{
|
||||
++i;
|
||||
ch = str[i];
|
||||
var index = chars.IndexOf(ch);
|
||||
if (index != -1)
|
||||
builder.Append(replacements[index]);
|
||||
else
|
||||
builder.Append(ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(ch);
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static bool Contains(this string str, char ch)
|
||||
{
|
||||
if (str == null)
|
||||
return false;
|
||||
return str.IndexOf(ch) != -1;
|
||||
}
|
||||
|
||||
public static bool Contains(this string str, string text, StringComparison comparison)
|
||||
{
|
||||
if (str == null)
|
||||
return false;
|
||||
return str.IndexOf(text, comparison) != -1;
|
||||
}
|
||||
|
||||
public static string GetPlural(this string str)
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
|
||||
switch (str)
|
||||
{
|
||||
case "Mouse": return "Mice";
|
||||
case "mouse": return "mice";
|
||||
case "Axis": return "Axes";
|
||||
case "axis": return "axes";
|
||||
}
|
||||
|
||||
return str + 's';
|
||||
}
|
||||
|
||||
public static string NicifyMemorySize(long numBytes)
|
||||
{
|
||||
// Gigabytes.
|
||||
if (numBytes > 1024 * 1024 * 1024)
|
||||
{
|
||||
var gb = numBytes / (1024 * 1024 * 1024);
|
||||
var remainder = (numBytes % (1024 * 1024 * 1024)) / 1.0f;
|
||||
|
||||
return $"{gb + remainder} GB";
|
||||
}
|
||||
|
||||
// Megabytes.
|
||||
if (numBytes > 1024 * 1024)
|
||||
{
|
||||
var mb = numBytes / (1024 * 1024);
|
||||
var remainder = (numBytes % (1024 * 1024)) / 1.0f;
|
||||
|
||||
return $"{mb + remainder} MB";
|
||||
}
|
||||
|
||||
// Kilobytes.
|
||||
if (numBytes > 1024)
|
||||
{
|
||||
var kb = numBytes / 1024;
|
||||
var remainder = (numBytes % 1024) / 1.0f;
|
||||
|
||||
return $"{kb + remainder} KB";
|
||||
}
|
||||
|
||||
// Bytes.
|
||||
return $"{numBytes} Bytes";
|
||||
}
|
||||
|
||||
public static bool FromNicifiedMemorySize(string text, out long result, long defaultMultiplier = 1)
|
||||
{
|
||||
text = text.Trim();
|
||||
|
||||
var multiplier = defaultMultiplier;
|
||||
if (text.EndsWith("MB", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1024 * 1024;
|
||||
text = text.Substring(0, text.Length - 2);
|
||||
}
|
||||
else if (text.EndsWith("GB", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1024 * 1024 * 1024;
|
||||
text = text.Substring(0, text.Length - 2);
|
||||
}
|
||||
else if (text.EndsWith("KB", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1024;
|
||||
text = text.Substring(0, text.Length - 2);
|
||||
}
|
||||
else if (text.EndsWith("Bytes", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1;
|
||||
text = text.Substring(0, text.Length - "Bytes".Length);
|
||||
}
|
||||
|
||||
if (!long.TryParse(text, out var num))
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = num * multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int CountOccurrences(this string str, char ch)
|
||||
{
|
||||
if (str == null)
|
||||
return 0;
|
||||
|
||||
var length = str.Length;
|
||||
var index = 0;
|
||||
var count = 0;
|
||||
|
||||
while (index < length)
|
||||
{
|
||||
var nextIndex = str.IndexOf(ch, index);
|
||||
if (nextIndex == -1)
|
||||
break;
|
||||
|
||||
++count;
|
||||
index = nextIndex + 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static IEnumerable<Substring> Tokenize(this string str)
|
||||
{
|
||||
var pos = 0;
|
||||
var length = str.Length;
|
||||
|
||||
while (pos < length)
|
||||
{
|
||||
while (pos < length && char.IsWhiteSpace(str[pos]))
|
||||
++pos;
|
||||
|
||||
if (pos == length)
|
||||
break;
|
||||
|
||||
if (str[pos] == '"')
|
||||
{
|
||||
++pos;
|
||||
var endPos = pos;
|
||||
while (endPos < length && str[endPos] != '\"')
|
||||
{
|
||||
// Doesn't recognize control sequences but allows escaping double quotes.
|
||||
if (str[endPos] == '\\' && endPos < length - 1)
|
||||
++endPos;
|
||||
++endPos;
|
||||
}
|
||||
yield return new Substring(str, pos, endPos - pos);
|
||||
pos = endPos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var endPos = pos;
|
||||
while (endPos < length && !char.IsWhiteSpace(str[endPos]))
|
||||
++endPos;
|
||||
yield return new Substring(str, pos, endPos - pos);
|
||||
pos = endPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Split(this string str, Func<char, bool> predicate)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
yield break;
|
||||
|
||||
var length = str.Length;
|
||||
var position = 0;
|
||||
|
||||
while (position < length)
|
||||
{
|
||||
// Skip separator.
|
||||
var ch = str[position];
|
||||
if (predicate(ch))
|
||||
{
|
||||
++position;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip to next separator.
|
||||
var startPosition = position;
|
||||
++position;
|
||||
while (position < length)
|
||||
{
|
||||
ch = str[position];
|
||||
if (predicate(ch))
|
||||
break;
|
||||
++position;
|
||||
}
|
||||
var endPosition = position;
|
||||
|
||||
yield return str.Substring(startPosition, endPosition - startPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Join<TValue>(string separator, params TValue[] values)
|
||||
{
|
||||
return Join(values, separator);
|
||||
}
|
||||
|
||||
public static string Join<TValue>(IEnumerable<TValue> values, string separator)
|
||||
{
|
||||
// Optimize for there not being any values or only a single one
|
||||
// that needs no concatenation.
|
||||
var firstValue = default(string);
|
||||
var valueCount = 0;
|
||||
StringBuilder result = null;
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (value == null)
|
||||
continue;
|
||||
var str = value.ToString();
|
||||
if (string.IsNullOrEmpty(str))
|
||||
continue;
|
||||
|
||||
++valueCount;
|
||||
if (valueCount == 1)
|
||||
{
|
||||
firstValue = str;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (valueCount == 2)
|
||||
{
|
||||
result = new StringBuilder();
|
||||
result.Append(firstValue);
|
||||
}
|
||||
|
||||
result.Append(separator);
|
||||
result.Append(str);
|
||||
}
|
||||
|
||||
if (valueCount == 0)
|
||||
return null;
|
||||
if (valueCount == 1)
|
||||
return firstValue;
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
public static string MakeUniqueName<TExisting>(string baseName, IEnumerable<TExisting> existingSet,
|
||||
Func<TExisting, string> getNameFunc)
|
||||
{
|
||||
if (getNameFunc == null)
|
||||
throw new ArgumentNullException(nameof(getNameFunc));
|
||||
|
||||
if (existingSet == null)
|
||||
return baseName;
|
||||
|
||||
var name = baseName;
|
||||
var nameIsUnique = false;
|
||||
var namesTried = 1;
|
||||
|
||||
// If the name ends in digits, start counting from the given number.
|
||||
if (baseName.Length > 0)
|
||||
{
|
||||
var lastDigit = baseName.Length;
|
||||
while (lastDigit > 0 && char.IsDigit(baseName[lastDigit - 1]))
|
||||
--lastDigit;
|
||||
if (lastDigit != baseName.Length)
|
||||
{
|
||||
namesTried = int.Parse(baseName.Substring(lastDigit)) + 1;
|
||||
baseName = baseName.Substring(0, lastDigit);
|
||||
}
|
||||
}
|
||||
|
||||
// Find unique name.
|
||||
while (!nameIsUnique)
|
||||
{
|
||||
nameIsUnique = true;
|
||||
foreach (var existing in existingSet)
|
||||
{
|
||||
var existingName = getNameFunc(existing);
|
||||
if (existingName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
name = $"{baseName}{namesTried}";
|
||||
nameIsUnique = false;
|
||||
++namesTried;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
////REVIEW: should we allow whitespace and skip automatically?
|
||||
public static bool CharacterSeparatedListsHaveAtLeastOneCommonElement(string firstList, string secondList,
|
||||
char separator)
|
||||
{
|
||||
if (firstList == null)
|
||||
throw new ArgumentNullException(nameof(firstList));
|
||||
if (secondList == null)
|
||||
throw new ArgumentNullException(nameof(secondList));
|
||||
|
||||
// Go element by element through firstList and try to find a matching
|
||||
// element in secondList.
|
||||
var indexInFirst = 0;
|
||||
var lengthOfFirst = firstList.Length;
|
||||
var lengthOfSecond = secondList.Length;
|
||||
while (indexInFirst < lengthOfFirst)
|
||||
{
|
||||
// Skip empty elements.
|
||||
if (firstList[indexInFirst] == separator)
|
||||
++indexInFirst;
|
||||
|
||||
// Find end of current element.
|
||||
var endIndexInFirst = indexInFirst + 1;
|
||||
while (endIndexInFirst < lengthOfFirst && firstList[endIndexInFirst] != separator)
|
||||
++endIndexInFirst;
|
||||
var lengthOfCurrentInFirst = endIndexInFirst - indexInFirst;
|
||||
|
||||
// Go through element in secondList and match it to the current
|
||||
// element.
|
||||
var indexInSecond = 0;
|
||||
while (indexInSecond < lengthOfSecond)
|
||||
{
|
||||
// Skip empty elements.
|
||||
if (secondList[indexInSecond] == separator)
|
||||
++indexInSecond;
|
||||
|
||||
// Find end of current element.
|
||||
var endIndexInSecond = indexInSecond + 1;
|
||||
while (endIndexInSecond < lengthOfSecond && secondList[endIndexInSecond] != separator)
|
||||
++endIndexInSecond;
|
||||
var lengthOfCurrentInSecond = endIndexInSecond - indexInSecond;
|
||||
|
||||
// If length matches, do character-by-character comparison.
|
||||
if (lengthOfCurrentInFirst == lengthOfCurrentInSecond)
|
||||
{
|
||||
var startIndexInFirst = indexInFirst;
|
||||
var startIndexInSecond = indexInSecond;
|
||||
|
||||
var isMatch = true;
|
||||
for (var i = 0; i < lengthOfCurrentInFirst; ++i)
|
||||
{
|
||||
var first = firstList[startIndexInFirst + i];
|
||||
var second = secondList[startIndexInSecond + i];
|
||||
|
||||
if (char.ToLowerInvariant(first) != char.ToLowerInvariant(second))
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMatch)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not a match so go to next.
|
||||
indexInSecond = endIndexInSecond + 1;
|
||||
}
|
||||
|
||||
// Go to next element.
|
||||
indexInFirst = endIndexInFirst + 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse an int at the given position in the string.
|
||||
// Unlike int.Parse(), does not require allocating a new string containing only
|
||||
// the substring with the number.
|
||||
public static int ParseInt(string str, int pos)
|
||||
{
|
||||
var multiply = 1;
|
||||
var result = 0;
|
||||
var length = str.Length;
|
||||
|
||||
while (pos < length)
|
||||
{
|
||||
var ch = str[pos];
|
||||
var digit = ch - '0';
|
||||
if (digit < 0 || digit > 9)
|
||||
break;
|
||||
|
||||
result = result * multiply + digit;
|
||||
|
||||
multiply *= 10;
|
||||
++pos;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////TODO: this should use UTF-8 and not UTF-16
|
||||
|
||||
public static bool WriteStringToBuffer(string text, IntPtr buffer, int bufferSizeInCharacters)
|
||||
{
|
||||
uint offset = 0;
|
||||
return WriteStringToBuffer(text, buffer, bufferSizeInCharacters, ref offset);
|
||||
}
|
||||
|
||||
public static unsafe bool WriteStringToBuffer(string text, IntPtr buffer, int bufferSizeInCharacters, ref uint offset)
|
||||
{
|
||||
if (buffer == IntPtr.Zero)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
var length = string.IsNullOrEmpty(text) ? 0 : text.Length;
|
||||
if (length > ushort.MaxValue)
|
||||
throw new ArgumentException(string.Format("String exceeds max size of {0} characters", ushort.MaxValue), "text");
|
||||
|
||||
var endOffset = offset + sizeof(char) * length + sizeof(int);
|
||||
if (endOffset > bufferSizeInCharacters)
|
||||
return false;
|
||||
|
||||
var ptr = ((byte*)buffer) + offset;
|
||||
*((ushort*)ptr) = (ushort)length;
|
||||
ptr += sizeof(ushort);
|
||||
|
||||
for (var i = 0; i < length; ++i, ptr += sizeof(char))
|
||||
*((char*)ptr) = text[i];
|
||||
|
||||
offset = (uint)endOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string ReadStringFromBuffer(IntPtr buffer, int bufferSize)
|
||||
{
|
||||
uint offset = 0;
|
||||
return ReadStringFromBuffer(buffer, bufferSize, ref offset);
|
||||
}
|
||||
|
||||
public static unsafe string ReadStringFromBuffer(IntPtr buffer, int bufferSize, ref uint offset)
|
||||
{
|
||||
if (buffer == IntPtr.Zero)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
if (offset + sizeof(int) > bufferSize)
|
||||
return null;
|
||||
|
||||
var ptr = ((byte*)buffer) + offset;
|
||||
var length = *((ushort*)ptr);
|
||||
ptr += sizeof(ushort);
|
||||
|
||||
if (length == 0)
|
||||
return null;
|
||||
|
||||
var endOffset = offset + sizeof(char) * length + sizeof(int);
|
||||
if (endOffset > bufferSize)
|
||||
return null;
|
||||
|
||||
var text = Marshal.PtrToStringUni(new IntPtr(ptr), length);
|
||||
|
||||
offset = (uint)endOffset;
|
||||
return text;
|
||||
}
|
||||
|
||||
public static bool IsPrintable(this char ch)
|
||||
{
|
||||
// This is crude and far from how Unicode defines printable but it should serve as a good enough approximation.
|
||||
return !char.IsControl(ch) && !char.IsWhiteSpace(ch);
|
||||
}
|
||||
|
||||
public static string WithAllWhitespaceStripped(this string str)
|
||||
{
|
||||
var buffer = new StringBuilder();
|
||||
foreach (var ch in str)
|
||||
if (!char.IsWhiteSpace(ch))
|
||||
buffer.Append(ch);
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
public static bool InvariantEqualsIgnoreCase(this string left, string right)
|
||||
{
|
||||
if (string.IsNullOrEmpty(left))
|
||||
return string.IsNullOrEmpty(right);
|
||||
return string.Equals(left, right, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public static string ExpandTemplateString(string template, Func<string, string> mapFunc)
|
||||
{
|
||||
if (string.IsNullOrEmpty(template))
|
||||
throw new ArgumentNullException(nameof(template));
|
||||
if (mapFunc == null)
|
||||
throw new ArgumentNullException(nameof(mapFunc));
|
||||
|
||||
var buffer = new StringBuilder();
|
||||
|
||||
var length = template.Length;
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
var ch = template[i];
|
||||
if (ch != '{')
|
||||
{
|
||||
buffer.Append(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
++i;
|
||||
var tokenStartPos = i;
|
||||
while (i < length && template[i] != '}')
|
||||
++i;
|
||||
var token = template.Substring(tokenStartPos, i - tokenStartPos);
|
||||
// Loop increment will skip closing '}'.
|
||||
|
||||
var mapped = mapFunc(token);
|
||||
buffer.Append(mapped);
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54b255b27c8247b4b51e8df1f30af3c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
// Work with substrings without actually allocating strings.
|
||||
internal struct Substring : IComparable<Substring>, IEquatable<Substring>
|
||||
{
|
||||
private readonly string m_String;
|
||||
private readonly int m_Index;
|
||||
private readonly int m_Length;
|
||||
|
||||
public bool isEmpty => m_Length == 0;
|
||||
|
||||
public Substring(string str)
|
||||
{
|
||||
m_String = str;
|
||||
m_Index = 0;
|
||||
if (str != null)
|
||||
m_Length = str.Length;
|
||||
else
|
||||
m_Length = 0;
|
||||
}
|
||||
|
||||
public Substring(string str, int index, int length)
|
||||
{
|
||||
Debug.Assert(str == null || index < str.Length);
|
||||
Debug.Assert(str != null || length == 0);
|
||||
|
||||
m_String = str;
|
||||
m_Index = index;
|
||||
m_Length = length;
|
||||
}
|
||||
|
||||
public Substring(string str, int index)
|
||||
{
|
||||
Debug.Assert(str == null || index < str.Length);
|
||||
|
||||
m_String = str;
|
||||
m_Index = index;
|
||||
m_Length = str.Length - index;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Substring other)
|
||||
return Equals(other);
|
||||
|
||||
if (obj is string str)
|
||||
return Equals(str);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(string other)
|
||||
{
|
||||
if (string.IsNullOrEmpty(other))
|
||||
return m_Length == 0;
|
||||
|
||||
if (other.Length != m_Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < m_Length; ++i)
|
||||
if (other[i] != m_String[m_Index + i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Equals(Substring other)
|
||||
{
|
||||
return CompareTo(other) == 0;
|
||||
}
|
||||
|
||||
public bool Equals(InternedString other)
|
||||
{
|
||||
if (length != other.length)
|
||||
return false;
|
||||
|
||||
return string.Compare(m_String, m_Index, other.ToString(), 0, length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
|
||||
public int CompareTo(Substring other)
|
||||
{
|
||||
return Compare(this, other, StringComparison.CurrentCulture);
|
||||
}
|
||||
|
||||
public static int Compare(Substring left, Substring right, StringComparison comparison)
|
||||
{
|
||||
if (left.m_Length != right.m_Length)
|
||||
{
|
||||
if (left.m_Length < right.m_Length)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return string.Compare(left.m_String, left.m_Index, right.m_String, right.m_Index, left.m_Length,
|
||||
comparison);
|
||||
}
|
||||
|
||||
public bool StartsWith(string str)
|
||||
{
|
||||
if (str.Length > length)
|
||||
return false;
|
||||
for (var i = 0; i < str.Length; ++i)
|
||||
if (m_String[m_Index + i] != str[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public string Substr(int index = 0, int length = -1)
|
||||
{
|
||||
if (length < 0)
|
||||
length = this.length - index;
|
||||
return m_String.Substring(m_Index + index, length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (m_String == null)
|
||||
return string.Empty;
|
||||
|
||||
return m_String.Substring(m_Index, m_Length);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (m_String == null)
|
||||
return 0;
|
||||
|
||||
if (m_Index == 0 && m_Length == m_String.Length)
|
||||
return m_String.GetHashCode();
|
||||
|
||||
////FIXME: this is bad... shouldn't allocate
|
||||
return ToString().GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator==(Substring a, Substring b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(Substring a, Substring b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator==(Substring a, InternedString b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(Substring a, InternedString b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator==(InternedString a, Substring b)
|
||||
{
|
||||
return b.Equals(a);
|
||||
}
|
||||
|
||||
public static bool operator!=(InternedString a, Substring b)
|
||||
{
|
||||
return !b.Equals(a);
|
||||
}
|
||||
|
||||
public static implicit operator Substring(string s)
|
||||
{
|
||||
return new Substring(s);
|
||||
}
|
||||
|
||||
public int length => m_Length;
|
||||
|
||||
public int index => m_Index;
|
||||
|
||||
public char this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= m_Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return m_String[m_Index + index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f04ed13d477436fa6a48ff5d0d46d8c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class TypeHelpers
|
||||
{
|
||||
public static TObject As<TObject>(this object obj)
|
||||
{
|
||||
// This avoid NREs for value types. For example, trying to do "(Vector3)null" will
|
||||
// result in an NRE. Doing "null.As<Vector3>()" will result in "default(Vector3)".
|
||||
if (obj == null)
|
||||
return default;
|
||||
return (TObject)obj;
|
||||
}
|
||||
|
||||
public static bool IsInt(this TypeCode type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TypeCode.Byte: return true;
|
||||
case TypeCode.SByte: return true;
|
||||
case TypeCode.Int16: return true;
|
||||
case TypeCode.Int32: return true;
|
||||
case TypeCode.Int64: return true;
|
||||
case TypeCode.UInt16: return true;
|
||||
case TypeCode.UInt32: return true;
|
||||
case TypeCode.UInt64: return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Type GetValueType(MemberInfo member)
|
||||
{
|
||||
var field = member as FieldInfo;
|
||||
if (field != null)
|
||||
return field.FieldType;
|
||||
|
||||
var property = member as PropertyInfo;
|
||||
if (property != null)
|
||||
return property.PropertyType;
|
||||
|
||||
var method = member as MethodInfo;
|
||||
if (method != null)
|
||||
return method.ReturnType;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetNiceTypeName(this Type type)
|
||||
{
|
||||
if (type.IsPrimitive)
|
||||
{
|
||||
if (type == typeof(int))
|
||||
return "int";
|
||||
if (type == typeof(float))
|
||||
return "float";
|
||||
if (type == typeof(char))
|
||||
return "char";
|
||||
if (type == typeof(byte))
|
||||
return "byte";
|
||||
if (type == typeof(short))
|
||||
return "short";
|
||||
if (type == typeof(long))
|
||||
return "long";
|
||||
if (type == typeof(double))
|
||||
return "double";
|
||||
if (type == typeof(uint))
|
||||
return "uint";
|
||||
if (type == typeof(sbyte))
|
||||
return "sbyte";
|
||||
if (type == typeof(ushort))
|
||||
return "ushort";
|
||||
if (type == typeof(ulong))
|
||||
return "ulong";
|
||||
}
|
||||
|
||||
return type.Name;
|
||||
}
|
||||
|
||||
public static Type GetGenericTypeArgumentFromHierarchy(Type type, Type genericTypeDefinition, int argumentIndex)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
if (genericTypeDefinition == null)
|
||||
throw new ArgumentNullException(nameof(genericTypeDefinition));
|
||||
if (argumentIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(argumentIndex));
|
||||
|
||||
if (genericTypeDefinition.IsInterface)
|
||||
{
|
||||
// Walk up the chain until we find the generic type def as an interface on a type.
|
||||
while (true)
|
||||
{
|
||||
var interfaces = type.GetInterfaces();
|
||||
var haveFoundInterface = false;
|
||||
foreach (var element in interfaces)
|
||||
{
|
||||
if (element.IsConstructedGenericType &&
|
||||
element.GetGenericTypeDefinition() == genericTypeDefinition)
|
||||
{
|
||||
type = element;
|
||||
haveFoundInterface = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Recurse into interface in case we're looking for a base interface.
|
||||
var typeArgument =
|
||||
GetGenericTypeArgumentFromHierarchy(element, genericTypeDefinition, argumentIndex);
|
||||
if (typeArgument != null)
|
||||
return typeArgument;
|
||||
}
|
||||
|
||||
if (haveFoundInterface)
|
||||
break;
|
||||
|
||||
type = type.BaseType;
|
||||
if (type == null || type == typeof(object))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Walk up the chain until we find the generic type def.
|
||||
while (!type.IsConstructedGenericType || type.GetGenericTypeDefinition() != genericTypeDefinition)
|
||||
{
|
||||
type = type.BaseType;
|
||||
if (type == typeof(object))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return type.GenericTypeArguments[argumentIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7eca76cf669745feb0c71655dbad352f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using System.ComponentModel;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A table mapping names to types in a case-insensitive mapping.
|
||||
/// </summary>
|
||||
internal struct TypeTable
|
||||
{
|
||||
public Dictionary<InternedString, Type> table;
|
||||
|
||||
public IEnumerable<string> names => table.Keys.Select(x => x.ToString());
|
||||
public IEnumerable<InternedString> internedNames => table.Keys;
|
||||
|
||||
// In the editor, we want to keep track of when the same type gets registered multiple times
|
||||
// with different names so that we can keep the aliases out of the UI.
|
||||
#if UNITY_EDITOR
|
||||
public HashSet<InternedString> aliases;
|
||||
#endif
|
||||
|
||||
// Strong coupling to Input Manager which is always the owner
|
||||
private InputManager m_Manager;
|
||||
|
||||
public void Initialize(InputManager manager)
|
||||
{
|
||||
table = new Dictionary<InternedString, Type>();
|
||||
#if UNITY_EDITOR
|
||||
aliases = new HashSet<InternedString>();
|
||||
#endif
|
||||
m_Manager = manager;
|
||||
}
|
||||
|
||||
public InternedString FindNameForType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
foreach (var pair in table)
|
||||
if (pair.Value == type)
|
||||
return pair.Key;
|
||||
return new InternedString();
|
||||
}
|
||||
|
||||
public void AddTypeRegistration(string name, Type type)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentException("Name cannot be null or empty", nameof(name));
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
var internedName = new InternedString(name);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// First check if the name has already been added to the table
|
||||
// This allows safely calling this function multiple times with same name
|
||||
// and prevents adding an alias to itself
|
||||
Type originalType;
|
||||
if (table.TryGetValue(internedName, out originalType))
|
||||
{
|
||||
// We don't warn about overriding any existing name/type mapping as that was previously supported
|
||||
table.Remove(internedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (table.ContainsValue(type))
|
||||
aliases.Add(internedName);
|
||||
}
|
||||
#endif
|
||||
|
||||
table[internedName] = type;
|
||||
}
|
||||
|
||||
public Type LookupTypeRegistration(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return null;
|
||||
|
||||
if (table == null)
|
||||
throw new InvalidOperationException("Input System not yet initialized");
|
||||
|
||||
return TryLookupTypeRegistration(new InternedString(name));
|
||||
}
|
||||
|
||||
private Type TryLookupTypeRegistration(InternedString internedName)
|
||||
{
|
||||
if (!table.TryGetValue(internedName, out var type))
|
||||
{
|
||||
// Failed to look-up type, either type do not exist or it is a custom type that has not been registered.
|
||||
// Check whether we have attempted to load custom types and otherwise lazily load types only when
|
||||
// relevant and reattempt looking up type by name. (ISXB-1766)
|
||||
if (m_Manager != null)
|
||||
{
|
||||
if (m_Manager.RegisterCustomTypes())
|
||||
table.TryGetValue(internedName, out type);
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public bool ShouldHideInUI(string name)
|
||||
{
|
||||
// Always hide aliases.
|
||||
if (aliases.Contains(new InternedString(name)))
|
||||
return true;
|
||||
|
||||
// Hide entries that have [DesignTimeVisible(false)] on the type.
|
||||
var type = LookupTypeRegistration(name);
|
||||
var attribute = type?.GetCustomAttribute<DesignTimeVisibleAttribute>();
|
||||
return !(attribute?.Visible ?? true);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ec5135f9c8612d42a83f87500580afa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user