123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using System.Text;
- using Unity.Collections;
- using Unity.Collections.LowLevel.Unsafe;
- using UnityEngine.InputSystem.Utilities;
-
- ////TODO: add a device setup version to InputManager and add version check here to ensure we're not going out of sync
-
- ////REVIEW: can we have a read-only version of this
-
- ////REVIEW: move this to .LowLevel? this one is pretty peculiar to use and doesn't really work like what you'd expect given C#'s List<>
-
- namespace UnityEngine.InputSystem
- {
- /// <summary>
- /// Keep a list of <see cref="InputControl"/>s without allocating managed memory.
- /// </summary>
- /// <remarks>
- /// This struct is mainly used by methods such as <see cref="InputSystem.FindControls(string)"/>
- /// or <see cref="InputControlPath.TryFindControls{TControl}"/> to store an arbitrary length
- /// list of resulting matches without having to allocate GC heap memory.
- ///
- /// Requires the control setup in the system to not change while the list is being used. If devices are
- /// removed from the system, the list will no longer be valid. Also, only works with controls of devices that
- /// have been added to the system (<see cref="InputDevice.added"/>). The reason for these constraints is
- /// that internally, the list only stores integer indices that are translates to <see cref="InputControl"/>
- /// references on the fly. If the device setup in the system changes, the indices may become invalid.
- ///
- /// This struct allocates unmanaged memory and thus must be disposed or it will leak memory. By default
- /// allocates <c>Allocator.Persistent</c> memory. You can direct it to use another allocator by
- /// passing an <see cref="Allocator"/> value to one of the constructors.
- ///
- /// <example>
- /// <code>
- /// // Find all controls with the "Submit" usage in the system.
- /// // By wrapping it in a `using` block, the list of controls will automatically be disposed at the end.
- /// using (var controls = InputSystem.FindControls("*/{Submit}"))
- /// /* ... */;
- /// </code>
- /// </example>
- /// </remarks>
- /// <typeparam name="TControl">Type of <see cref="InputControl"/> to store in the list.</typeparam>
- [DebuggerDisplay("Count = {Count}")]
- #if UNITY_EDITOR || DEVELOPMENT_BUILD
- [DebuggerTypeProxy(typeof(InputControlListDebugView<>))]
- #endif
- public unsafe struct InputControlList<TControl> : IList<TControl>, IReadOnlyList<TControl>, IDisposable
- where TControl : InputControl
- {
- /// <summary>
- /// Current number of controls in the list.
- /// </summary>
- /// <value>Number of controls currently in the list.</value>
- public int Count => m_Count;
-
- /// <summary>
- /// Total number of controls that can currently be stored in the list.
- /// </summary>
- /// <value>Total size of array as currently allocated.</value>
- /// <remarks>
- /// This can be set ahead of time to avoid repeated allocations.
- ///
- /// <example>
- /// <code>
- /// // Add all keys from the keyboard to a list.
- /// var keys = Keyboard.current.allKeys;
- /// var list = new InputControlList<KeyControl>(keys.Count);
- /// list.AddRange(keys);
- /// </code>
- /// </example>
- /// </remarks>
- public int Capacity
- {
- get
- {
- if (!m_Indices.IsCreated)
- return 0;
- return m_Indices.Length;
- }
- set
- {
- if (value < 0)
- throw new ArgumentException("Capacity cannot be negative", nameof(value));
-
- if (value == 0)
- {
- if (m_Count != 0)
- m_Indices.Dispose();
- m_Count = 0;
- return;
- }
-
- var newSize = value;
- var allocator = m_Allocator != Allocator.Invalid ? m_Allocator : Allocator.Persistent;
- ArrayHelpers.Resize(ref m_Indices, newSize, allocator);
- }
- }
-
- /// <summary>
- /// This is always false.
- /// </summary>
- /// <value>Always false.</value>
- public bool IsReadOnly => false;
-
- /// <summary>
- /// Return the control at the given index.
- /// </summary>
- /// <param name="index">Index of control.</param>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than or equal to <see cref="Count"/>
- /// </exception>
- /// <remarks>
- /// Internally, the list only stores indices. Resolution to <see cref="InputControl">controls</see> happens
- /// dynamically by looking them up globally.
- /// </remarks>
- public TControl this[int index]
- {
- get
- {
- if (index < 0 || index >= m_Count)
- throw new ArgumentOutOfRangeException(
- nameof(index), $"Index {index} is out of range in list with {m_Count} entries");
-
- return FromIndex(m_Indices[index]);
- }
- set
- {
- if (index < 0 || index >= m_Count)
- throw new ArgumentOutOfRangeException(
- nameof(index), $"Index {index} is out of range in list with {m_Count} entries");
-
- m_Indices[index] = ToIndex(value);
- }
- }
-
- /// <summary>
- /// Construct a list that allocates unmanaged memory from the given allocator.
- /// </summary>
- /// <param name="allocator">Allocator to use for requesting unmanaged memory.</param>
- /// <param name="initialCapacity">If greater than zero, will immediately allocate
- /// memory and set <see cref="Capacity"/> accordingly.</param>
- /// <example>
- /// <code>
- /// // Create a control list that allocates from the temporary memory allocator.
- /// using (var list = new InputControlList(Allocator.Temp))
- /// {
- /// // Add all gamepads to the list.
- /// InputSystem.FindControls("<Gamepad>", ref list);
- /// }
- /// </code>
- /// </example>
- public InputControlList(Allocator allocator, int initialCapacity = 0)
- {
- m_Allocator = allocator;
- m_Indices = new NativeArray<ulong>();
- m_Count = 0;
-
- if (initialCapacity != 0)
- Capacity = initialCapacity;
- }
-
- /// <summary>
- /// Construct a list and populate it with the given values.
- /// </summary>
- /// <param name="values">Sequence of values to populate the list with.</param>
- /// <param name="allocator">Allocator to use for requesting unmanaged memory.</param>
- /// <exception cref="ArgumentNullException"><paramref name="values"/> is <c>null</c>.</exception>
- public InputControlList(IEnumerable<TControl> values, Allocator allocator = Allocator.Persistent)
- : this(allocator)
- {
- if (values == null)
- throw new ArgumentNullException(nameof(values));
-
- foreach (var value in values)
- Add(value);
- }
-
- /// <summary>
- /// Construct a list and add the given values to it.
- /// </summary>
- /// <param name="values">Sequence of controls to add to the list.</param>
- /// <exception cref="ArgumentNullException"><paramref name="values"/> is null.</exception>
- public InputControlList(params TControl[] values)
- : this()
- {
- if (values == null)
- throw new ArgumentNullException(nameof(values));
-
- var count = values.Length;
- Capacity = Mathf.Max(count, 10);
- for (var i = 0; i < count; ++i)
- Add(values[i]);
- }
-
- /// <summary>
- /// Resizes the list to be exactly <paramref name="size"/> entries. If this is less than the
- /// current <see cref="Count"/>, additional entries are dropped. If it is more than the
- /// current <see cref="Count"/>, additional <c>null</c> entries are appended to the list.
- /// </summary>
- /// <param name="size">The new value for <see cref="Count"/>.</param>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is negative.</exception>
- /// <remarks>
- /// <see cref="Capacity"/> is increased if necessary. It will, however, not be decreased if it
- /// is larger than <paramref name="size"/> entries.
- /// </remarks>
- public void Resize(int size)
- {
- if (size < 0)
- throw new ArgumentOutOfRangeException(nameof(size), "Size cannot be negative");
-
- if (Capacity < size)
- Capacity = size;
-
- // Initialize newly added entries (if any) such that they produce NULL entries.
- if (size > Count)
- UnsafeUtility.MemSet((byte*)m_Indices.GetUnsafePtr() + Count * sizeof(ulong), Byte.MaxValue, size - Count);
-
- m_Count = size;
- }
-
- /// <summary>
- /// Add a control to the list.
- /// </summary>
- /// <param name="item">Control to add. Allowed to be <c>null</c>.</param>
- /// <remarks>
- /// If necessary, <see cref="Capacity"/> will be increased.
- ///
- /// It is allowed to add nulls to the list. This can be useful, for example, when
- /// specific indices in the list correlate with specific matches and a given match
- /// needs to be marked as "matches nothing".
- /// </remarks>
- /// <seealso cref="Remove"/>
- public void Add(TControl item)
- {
- var index = ToIndex(item);
- var allocator = m_Allocator != Allocator.Invalid ? m_Allocator : Allocator.Persistent;
- ArrayHelpers.AppendWithCapacity(ref m_Indices, ref m_Count, index, allocator: allocator);
- }
-
- /// <summary>
- /// Add a slice of elements taken from the given list.
- /// </summary>
- /// <param name="list">List to take the slice of values from.</param>
- /// <param name="count">Number of elements to copy from <paramref name="list"/>.</param>
- /// <param name="destinationIndex">Starting index in the current control list to copy to.
- /// This can be beyond <see cref="Count"/> or even <see cref="Capacity"/>. Memory is allocated
- /// as needed.</param>
- /// <param name="sourceIndex">Source index in <paramref name="list"/> to start copying from.
- /// <paramref name="count"/> elements are copied starting at <paramref name="sourceIndex"/>.</param>
- /// <typeparam name="TList">Type of list. This is a type parameter to avoid boxing in case the
- /// given list is a struct (such as InputControlList itself).</typeparam>
- /// <exception cref="ArgumentOutOfRangeException">The range of <paramref name="count"/>
- /// and <paramref name="sourceIndex"/> is at least partially outside the range of values
- /// available in <paramref name="list"/>.</exception>
- public void AddSlice<TList>(TList list, int count = -1, int destinationIndex = -1, int sourceIndex = 0)
- where TList : IReadOnlyList<TControl>
- {
- if (count < 0)
- count = list.Count;
- if (destinationIndex < 0)
- destinationIndex = Count;
-
- if (count == 0)
- return;
- if (sourceIndex + count > list.Count)
- throw new ArgumentOutOfRangeException(nameof(count),
- $"Count of {count} elements starting at index {sourceIndex} exceeds length of list of {list.Count}");
-
- // Make space in the list.
- if (Capacity < m_Count + count)
- Capacity = Math.Max(m_Count + count, 10);
- if (destinationIndex < Count)
- NativeArray<ulong>.Copy(m_Indices, destinationIndex, m_Indices, destinationIndex + count,
- Count - destinationIndex);
-
- // Add elements.
- for (var i = 0; i < count; ++i)
- m_Indices[destinationIndex + i] = ToIndex(list[sourceIndex + i]);
- m_Count += count;
- }
-
- /// <summary>
- /// Add a sequence of controls to the list.
- /// </summary>
- /// <param name="list">Sequence of controls to add.</param>
- /// <param name="count">Number of controls from <paramref name="list"/> to add. If negative
- /// (default), all controls from <paramref name="list"/> will be added.</param>
- /// <param name="destinationIndex">Index in the control list to start inserting controls
- /// at. If negative (default), controls will be appended to the end of the control list.</param>
- /// <exception cref="ArgumentNullException"><paramref name="list"/> is <c>null</c>.</exception>
- /// <remarks>
- /// If <paramref name="count"/> is not supplied, <paramref name="list"/> will be iterated
- /// over twice.
- /// </remarks>
- public void AddRange(IEnumerable<TControl> list, int count = -1, int destinationIndex = -1)
- {
- if (list == null)
- throw new ArgumentNullException(nameof(list));
-
- if (count < 0)
- count = list.Count();
- if (destinationIndex < 0)
- destinationIndex = Count;
-
- if (count == 0)
- return;
-
- // Make space in the list.
- if (Capacity < m_Count + count)
- Capacity = Math.Max(m_Count + count, 10);
- if (destinationIndex < Count)
- NativeArray<ulong>.Copy(m_Indices, destinationIndex, m_Indices, destinationIndex + count,
- Count - destinationIndex);
-
- // Add elements.
- foreach (var element in list)
- {
- m_Indices[destinationIndex++] = ToIndex(element);
- ++m_Count;
- --count;
- if (count == 0)
- break;
- }
- }
-
- /// <summary>
- /// Remove a control from the list.
- /// </summary>
- /// <param name="item">Control to remove. Can be null.</param>
- /// <returns>True if the control was found in the list and removed, false otherwise.</returns>
- /// <seealso cref="Add"/>
- public bool Remove(TControl item)
- {
- if (m_Count == 0)
- return false;
-
- var index = ToIndex(item);
- for (var i = 0; i < m_Count; ++i)
- {
- if (m_Indices[i] == index)
- {
- ArrayHelpers.EraseAtWithCapacity(m_Indices, ref m_Count, i);
- return true;
- }
- }
-
- return false;
- }
-
- /// <summary>
- /// Remove the control at the given index.
- /// </summary>
- /// <param name="index">Index of control to remove.</param>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is negative or equal
- /// or greater than <see cref="Count"/>.</exception>
- public void RemoveAt(int index)
- {
- if (index < 0 || index >= m_Count)
- throw new ArgumentOutOfRangeException(
- nameof(index), $"Index {index} is out of range in list with {m_Count} elements");
-
- ArrayHelpers.EraseAtWithCapacity(m_Indices, ref m_Count, index);
- }
-
- public void CopyTo(TControl[] array, int arrayIndex)
- {
- throw new NotImplementedException();
- }
-
- public int IndexOf(TControl item)
- {
- return IndexOf(item, 0);
- }
-
- public int IndexOf(TControl item, int startIndex, int count = -1)
- {
- if (startIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(startIndex), "startIndex cannot be negative");
-
- if (m_Count == 0)
- return -1;
-
- if (count < 0)
- count = Mathf.Max(m_Count - startIndex, 0);
-
- if (startIndex + count > m_Count)
- throw new ArgumentOutOfRangeException(nameof(count));
-
- var index = ToIndex(item);
- var indices = (ulong*)m_Indices.GetUnsafeReadOnlyPtr();
-
- for (var i = 0; i < count; ++i)
- if (indices[startIndex + i] == index)
- return startIndex + i;
-
- return -1;
- }
-
- public void Insert(int index, TControl item)
- {
- throw new NotImplementedException();
- }
-
- public void Clear()
- {
- m_Count = 0;
- }
-
- public bool Contains(TControl item)
- {
- return IndexOf(item) != -1;
- }
-
- public bool Contains(TControl item, int startIndex, int count = -1)
- {
- return IndexOf(item, startIndex, count) != -1;
- }
-
- public void SwapElements(int index1, int index2)
- {
- if (index1 < 0 || index1 >= m_Count)
- throw new ArgumentOutOfRangeException(nameof(index1));
- if (index2 < 0 || index2 >= m_Count)
- throw new ArgumentOutOfRangeException(nameof(index2));
-
- if (index1 != index2)
- m_Indices.SwapElements(index1, index2);
- }
-
- public void Sort<TCompare>(int startIndex, int count, TCompare comparer)
- where TCompare : IComparer<TControl>
- {
- if (startIndex < 0 || startIndex >= Count)
- throw new ArgumentOutOfRangeException(nameof(startIndex));
- if (startIndex + count >= Count)
- throw new ArgumentOutOfRangeException(nameof(count));
-
- // Simple insertion sort.
- for (var i = 1; i < count; ++i)
- for (var j = i; j > 0 && comparer.Compare(this[j - 1], this[j]) < 0; --j)
- SwapElements(j, j - 1);
- }
-
- /// <summary>
- /// Convert the contents of the list to an array.
- /// </summary>
- /// <param name="dispose">If true, the control list will be disposed of as part of the operation, i.e.
- /// <see cref="Dispose"/> will be called as a side-effect.</param>
- /// <returns>An array mirroring the contents of the list. Not null.</returns>
- public TControl[] ToArray(bool dispose = false)
- {
- // Somewhat pointless to allocate an empty array if we have no elements instead
- // of returning null, but other ToArray() implementations work that way so we do
- // the same to avoid surprises.
-
- var result = new TControl[m_Count];
- for (var i = 0; i < m_Count; ++i)
- result[i] = this[i];
-
- if (dispose)
- Dispose();
-
- return result;
- }
-
- internal void AppendTo(ref TControl[] array, ref int count)
- {
- for (var i = 0; i < m_Count; ++i)
- ArrayHelpers.AppendWithCapacity(ref array, ref count, this[i]);
- }
-
- public void Dispose()
- {
- if (m_Indices.IsCreated)
- m_Indices.Dispose();
- }
-
- public IEnumerator<TControl> GetEnumerator()
- {
- return new Enumerator(this);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public override string ToString()
- {
- if (Count == 0)
- return "()";
-
- var builder = new StringBuilder();
- builder.Append('(');
-
- for (var i = 0; i < Count; ++i)
- {
- if (i != 0)
- builder.Append(',');
- builder.Append(this[i]);
- }
-
- builder.Append(')');
- return builder.ToString();
- }
-
- private int m_Count;
- private NativeArray<ulong> m_Indices;
- private readonly Allocator m_Allocator;
-
- private const ulong kInvalidIndex = 0xffffffffffffffff;
-
- private static ulong ToIndex(TControl control)
- {
- if (control == null)
- return kInvalidIndex;
-
- var device = control.device;
- var deviceId = device.m_DeviceId;
- var controlIndex = !ReferenceEquals(device, control)
- ? device.m_ChildrenForEachControl.IndexOfReference<InputControl, InputControl>(control) + 1
- : 0;
-
- // There is a known documented bug with the new Rosyln
- // compiler where it warns on casts with following line that
- // was perfectly legal in previous CSC compiler.
- // Below is silly conversion to get rid of warning, or we can pragma
- // out the warning.
- //return ((ulong)deviceId << 32) | (ulong)controlIndex;
- var shiftedDeviceId = (ulong)deviceId << 32;
- var unsignedControlIndex = (ulong)controlIndex;
-
- return shiftedDeviceId | unsignedControlIndex;
- }
-
- private static TControl FromIndex(ulong index)
- {
- if (index == kInvalidIndex)
- return null;
-
- var deviceId = (int)(index >> 32);
- var controlIndex = (int)(index & 0xFFFFFFFF);
-
- var device = InputSystem.GetDeviceById(deviceId);
- if (device == null)
- return null;
- if (controlIndex == 0)
- return (TControl)(InputControl)device;
-
- return (TControl)device.m_ChildrenForEachControl[controlIndex - 1];
- }
-
- private struct Enumerator : IEnumerator<TControl>
- {
- private readonly ulong* m_Indices;
- private readonly int m_Count;
- private int m_Current;
-
- public Enumerator(InputControlList<TControl> list)
- {
- m_Count = list.m_Count;
- m_Current = -1;
- m_Indices = m_Count > 0 ? (ulong*)list.m_Indices.GetUnsafeReadOnlyPtr() : null;
- }
-
- public bool MoveNext()
- {
- if (m_Current >= m_Count)
- return false;
- ++m_Current;
- return (m_Current != m_Count);
- }
-
- public void Reset()
- {
- m_Current = -1;
- }
-
- public TControl Current
- {
- get
- {
- if (m_Indices == null)
- throw new InvalidOperationException("Enumerator is not valid");
- return FromIndex(m_Indices[m_Current]);
- }
- }
-
- object IEnumerator.Current => Current;
-
- public void Dispose()
- {
- }
- }
- }
-
- #if UNITY_EDITOR || DEVELOPMENT_BUILD
- internal struct InputControlListDebugView<TControl>
- where TControl : InputControl
- {
- private readonly TControl[] m_Controls;
-
- public InputControlListDebugView(InputControlList<TControl> list)
- {
- m_Controls = list.ToArray();
- }
-
- public TControl[] controls => m_Controls;
- }
- #endif
- }
|