InlinedArray.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. ////REVIEW: what about ignoring 'firstValue' entirely in case length > 1 and putting everything into an array in that case
  6. namespace UnityEngine.InputSystem.Utilities
  7. {
  8. /// <summary>
  9. /// Helper to avoid array allocations if there's only a single value in the array.
  10. /// </summary>
  11. /// <remarks>
  12. /// Also, once more than one entry is necessary, allows treating the extra array as having capacity.
  13. /// This means that, for example, 5 or 10 entries can be allocated in batch rather than growing an
  14. /// array one by one.
  15. /// </remarks>
  16. /// <typeparam name="TValue">Element type for the array.</typeparam>
  17. internal struct InlinedArray<TValue> : IEnumerable<TValue>
  18. {
  19. // We inline the first value so if there's only one, there's
  20. // no additional allocation. If more are added, we allocate an array.
  21. public int length;
  22. public TValue firstValue;
  23. public TValue[] additionalValues;
  24. public int Capacity => additionalValues?.Length + 1 ?? 1;
  25. public InlinedArray(TValue value)
  26. {
  27. length = 1;
  28. firstValue = value;
  29. additionalValues = null;
  30. }
  31. public InlinedArray(TValue firstValue, params TValue[] additionalValues)
  32. {
  33. length = 1 + additionalValues.Length;
  34. this.firstValue = firstValue;
  35. this.additionalValues = additionalValues;
  36. }
  37. public InlinedArray(IEnumerable<TValue> values)
  38. : this()
  39. {
  40. length = values.Count();
  41. if (length > 1)
  42. additionalValues = new TValue[length - 1];
  43. else
  44. additionalValues = null;
  45. var index = 0;
  46. foreach (var value in values)
  47. {
  48. if (index == 0)
  49. firstValue = value;
  50. else
  51. additionalValues[index - 1] = value;
  52. ++index;
  53. }
  54. }
  55. public TValue this[int index]
  56. {
  57. get
  58. {
  59. if (index < 0 || index >= length)
  60. throw new ArgumentOutOfRangeException(nameof(index));
  61. if (index == 0)
  62. return firstValue;
  63. return additionalValues[index - 1];
  64. }
  65. set
  66. {
  67. if (index < 0 || index >= length)
  68. throw new ArgumentOutOfRangeException(nameof(index));
  69. if (index == 0)
  70. firstValue = value;
  71. else
  72. additionalValues[index - 1] = value;
  73. }
  74. }
  75. public void Clear()
  76. {
  77. length = 0;
  78. firstValue = default;
  79. additionalValues = null;
  80. }
  81. public void ClearWithCapacity()
  82. {
  83. firstValue = default;
  84. for (var i = 0; i < length - 1; ++i)
  85. additionalValues[i] = default;
  86. length = 0;
  87. }
  88. ////REVIEW: This is inconsistent with ArrayHelpers.Clone() which also clones elements
  89. public InlinedArray<TValue> Clone()
  90. {
  91. return new InlinedArray<TValue>
  92. {
  93. length = length,
  94. firstValue = firstValue,
  95. additionalValues = additionalValues != null ? ArrayHelpers.Copy(additionalValues) : null
  96. };
  97. }
  98. public void SetLength(int size)
  99. {
  100. // Null out everything we're cutting off.
  101. if (size < length)
  102. {
  103. for (var i = size; i < length; ++i)
  104. this[i] = default;
  105. }
  106. length = size;
  107. if (size > 1 && (additionalValues == null || additionalValues.Length < size - 1))
  108. Array.Resize(ref additionalValues, size - 1);
  109. }
  110. public TValue[] ToArray()
  111. {
  112. return ArrayHelpers.Join(firstValue, additionalValues);
  113. }
  114. public TOther[] ToArray<TOther>(Func<TValue, TOther> mapFunction)
  115. {
  116. if (length == 0)
  117. return null;
  118. var result = new TOther[length];
  119. for (var i = 0; i < length; ++i)
  120. result[i] = mapFunction(this[i]);
  121. return result;
  122. }
  123. public int IndexOf(TValue value)
  124. {
  125. var comparer = EqualityComparer<TValue>.Default;
  126. if (length > 0)
  127. {
  128. if (comparer.Equals(firstValue, value))
  129. return 0;
  130. if (additionalValues != null)
  131. {
  132. for (var i = 0; i < length - 1; ++i)
  133. if (comparer.Equals(additionalValues[i], value))
  134. return i + 1;
  135. }
  136. }
  137. return -1;
  138. }
  139. public int Append(TValue value)
  140. {
  141. if (length == 0)
  142. {
  143. firstValue = value;
  144. }
  145. else if (additionalValues == null)
  146. {
  147. additionalValues = new TValue[1];
  148. additionalValues[0] = value;
  149. }
  150. else
  151. {
  152. Array.Resize(ref additionalValues, length);
  153. additionalValues[length - 1] = value;
  154. }
  155. var index = length;
  156. ++length;
  157. return index;
  158. }
  159. public int AppendWithCapacity(TValue value, int capacityIncrement = 10)
  160. {
  161. if (length == 0)
  162. {
  163. firstValue = value;
  164. }
  165. else
  166. {
  167. var numAdditionalValues = length - 1;
  168. ArrayHelpers.AppendWithCapacity(ref additionalValues, ref numAdditionalValues, value, capacityIncrement: capacityIncrement);
  169. }
  170. var index = length;
  171. ++length;
  172. return index;
  173. }
  174. public void Append(IEnumerable<TValue> values)
  175. {
  176. foreach (var value in values)
  177. Append(value);
  178. }
  179. public void Remove(TValue value)
  180. {
  181. if (length < 1)
  182. return;
  183. if (EqualityComparer<TValue>.Default.Equals(firstValue, value))
  184. {
  185. RemoveAt(0);
  186. }
  187. else if (additionalValues != null)
  188. {
  189. for (var i = 0; i < length - 1; ++i)
  190. {
  191. if (EqualityComparer<TValue>.Default.Equals(additionalValues[i], value))
  192. {
  193. RemoveAt(i + 1);
  194. break;
  195. }
  196. }
  197. }
  198. }
  199. public void RemoveAtWithCapacity(int index)
  200. {
  201. if (index < 0 || index >= length)
  202. throw new ArgumentOutOfRangeException(nameof(index));
  203. if (index == 0)
  204. {
  205. if (length == 1)
  206. {
  207. firstValue = default;
  208. }
  209. else if (length == 2)
  210. {
  211. firstValue = additionalValues[0];
  212. additionalValues[0] = default;
  213. }
  214. else
  215. {
  216. Debug.Assert(length > 2);
  217. firstValue = additionalValues[0];
  218. var numAdditional = length - 1;
  219. ArrayHelpers.EraseAtWithCapacity(additionalValues, ref numAdditional, 0);
  220. }
  221. }
  222. else
  223. {
  224. var numAdditional = length - 1;
  225. ArrayHelpers.EraseAtWithCapacity(additionalValues, ref numAdditional, index - 1);
  226. }
  227. --length;
  228. }
  229. public void RemoveAt(int index)
  230. {
  231. if (index < 0 || index >= length)
  232. throw new ArgumentOutOfRangeException(nameof(index));
  233. if (index == 0)
  234. {
  235. if (additionalValues != null)
  236. {
  237. firstValue = additionalValues[0];
  238. if (additionalValues.Length == 1)
  239. additionalValues = null;
  240. else
  241. {
  242. Array.Copy(additionalValues, 1, additionalValues, 0, additionalValues.Length - 1);
  243. Array.Resize(ref additionalValues, additionalValues.Length - 1);
  244. }
  245. }
  246. else
  247. {
  248. firstValue = default;
  249. }
  250. }
  251. else
  252. {
  253. Debug.Assert(additionalValues != null);
  254. var numAdditionalValues = length - 1;
  255. if (numAdditionalValues == 1)
  256. {
  257. // Remove only entry in array.
  258. additionalValues = null;
  259. }
  260. else if (index == length - 1)
  261. {
  262. // Remove entry at end.
  263. Array.Resize(ref additionalValues, numAdditionalValues - 1);
  264. }
  265. else
  266. {
  267. // Remove entry at beginning or in middle by pasting together
  268. // into a new array.
  269. var newAdditionalValues = new TValue[numAdditionalValues - 1];
  270. if (index >= 2)
  271. {
  272. // Copy elements before entry.
  273. Array.Copy(additionalValues, 0, newAdditionalValues, 0, index - 1);
  274. }
  275. // Copy elements after entry. We already know that we're not removing
  276. // the last entry so there have to be entries.
  277. Array.Copy(additionalValues, index + 1 - 1, newAdditionalValues, index - 1,
  278. length - index - 1);
  279. additionalValues = newAdditionalValues;
  280. }
  281. }
  282. --length;
  283. }
  284. public void RemoveAtByMovingTailWithCapacity(int index)
  285. {
  286. if (index < 0 || index >= length)
  287. throw new ArgumentOutOfRangeException(nameof(index));
  288. var numAdditionalValues = length - 1;
  289. if (index == 0)
  290. {
  291. if (length > 1)
  292. {
  293. firstValue = additionalValues[numAdditionalValues - 1];
  294. additionalValues[numAdditionalValues - 1] = default;
  295. }
  296. else
  297. {
  298. firstValue = default;
  299. }
  300. }
  301. else
  302. {
  303. Debug.Assert(additionalValues != null);
  304. ArrayHelpers.EraseAtByMovingTail(additionalValues, ref numAdditionalValues, index - 1);
  305. }
  306. --length;
  307. }
  308. public bool RemoveByMovingTailWithCapacity(TValue value)
  309. {
  310. var index = IndexOf(value);
  311. if (index == -1)
  312. return false;
  313. RemoveAtByMovingTailWithCapacity(index);
  314. return true;
  315. }
  316. public bool Contains(TValue value, IEqualityComparer<TValue> comparer)
  317. {
  318. for (var n = 0; n < length; ++n)
  319. if (comparer.Equals(this[n], value))
  320. return true;
  321. return false;
  322. }
  323. public void Merge(InlinedArray<TValue> other)
  324. {
  325. var comparer = EqualityComparer<TValue>.Default;
  326. for (var i = 0; i < other.length; ++i)
  327. {
  328. var value = other[i];
  329. if (Contains(value, comparer))
  330. continue;
  331. ////FIXME: this is ugly as it repeatedly copies
  332. Append(value);
  333. }
  334. }
  335. public IEnumerator<TValue> GetEnumerator()
  336. {
  337. return new Enumerator { array = this, index = -1 };
  338. }
  339. IEnumerator IEnumerable.GetEnumerator()
  340. {
  341. return GetEnumerator();
  342. }
  343. private struct Enumerator : IEnumerator<TValue>
  344. {
  345. public InlinedArray<TValue> array;
  346. public int index;
  347. public bool MoveNext()
  348. {
  349. if (index >= array.length)
  350. return false;
  351. ++index;
  352. return index < array.length;
  353. }
  354. public void Reset()
  355. {
  356. index = -1;
  357. }
  358. public TValue Current => array[index];
  359. object IEnumerator.Current => Current;
  360. public void Dispose()
  361. {
  362. }
  363. }
  364. }
  365. internal static class InputArrayExtensions
  366. {
  367. public static int IndexOfReference<TValue>(this InlinedArray<TValue> array, TValue value)
  368. where TValue : class
  369. {
  370. for (var i = 0; i < array.length; ++i)
  371. if (ReferenceEquals(array[i], value))
  372. return i;
  373. return -1;
  374. }
  375. public static bool Contains<TValue>(this InlinedArray<TValue> array, TValue value)
  376. {
  377. for (var i = 0; i < array.length; ++i)
  378. if (array[i].Equals(value))
  379. return true;
  380. return false;
  381. }
  382. public static bool ContainsReference<TValue>(this InlinedArray<TValue> array, TValue value)
  383. where TValue : class
  384. {
  385. return IndexOfReference(array, value) != -1;
  386. }
  387. }
  388. }