Ingen beskrivning
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

UnsafeHashMap.cs 37KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Runtime.CompilerServices;
  6. using Unity.Mathematics;
  7. using Unity.Jobs;
  8. using System.Runtime.InteropServices;
  9. using Unity.Jobs.LowLevel.Unsafe;
  10. namespace Unity.Collections.LowLevel.Unsafe
  11. {
  12. [StructLayout(LayoutKind.Sequential)]
  13. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  14. internal unsafe struct HashMapHelper<TKey>
  15. where TKey : unmanaged, IEquatable<TKey>
  16. {
  17. [NativeDisableUnsafePtrRestriction]
  18. internal byte* Ptr;
  19. [NativeDisableUnsafePtrRestriction]
  20. internal TKey* Keys;
  21. [NativeDisableUnsafePtrRestriction]
  22. internal int* Next;
  23. [NativeDisableUnsafePtrRestriction]
  24. internal int* Buckets;
  25. internal int Count;
  26. internal int Capacity;
  27. internal int Log2MinGrowth;
  28. internal int BucketCapacity;
  29. internal int AllocatedIndex;
  30. internal int FirstFreeIdx;
  31. internal int SizeOfTValue;
  32. internal AllocatorManager.AllocatorHandle Allocator;
  33. internal const int kMinimumCapacity = 256;
  34. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  35. internal int CalcCapacityCeilPow2(int capacity)
  36. {
  37. capacity = math.max(math.max(1, Count), capacity);
  38. var newCapacity = math.max(capacity, 1 << Log2MinGrowth);
  39. var result = math.ceilpow2(newCapacity);
  40. return result;
  41. }
  42. internal static int GetBucketSize(int capacity)
  43. {
  44. return capacity * 2;
  45. }
  46. internal readonly bool IsCreated
  47. {
  48. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  49. get => Ptr != null;
  50. }
  51. internal readonly bool IsEmpty
  52. {
  53. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  54. get => !IsCreated || Count == 0;
  55. }
  56. internal void Clear()
  57. {
  58. UnsafeUtility.MemSet(Buckets, 0xff, BucketCapacity * sizeof(int));
  59. UnsafeUtility.MemSet(Next, 0xff, Capacity * sizeof(int));
  60. Count = 0;
  61. FirstFreeIdx = -1;
  62. AllocatedIndex = 0;
  63. }
  64. internal void Init(int capacity, int sizeOfValueT, int minGrowth, AllocatorManager.AllocatorHandle allocator)
  65. {
  66. Count = 0;
  67. Log2MinGrowth = (byte)(32 - math.lzcnt(math.max(1, minGrowth) - 1));
  68. capacity = CalcCapacityCeilPow2(capacity);
  69. Capacity = capacity;
  70. BucketCapacity = GetBucketSize(capacity);
  71. Allocator = allocator;
  72. SizeOfTValue = sizeOfValueT;
  73. int keyOffset, nextOffset, bucketOffset;
  74. int totalSize = CalculateDataSize(capacity, BucketCapacity, sizeOfValueT, out keyOffset, out nextOffset, out bucketOffset);
  75. Ptr = (byte*)Memory.Unmanaged.Allocate(totalSize, JobsUtility.CacheLineSize, allocator);
  76. Keys = (TKey*)(Ptr + keyOffset);
  77. Next = (int*)(Ptr + nextOffset);
  78. Buckets = (int*)(Ptr + bucketOffset);
  79. Clear();
  80. }
  81. internal void Dispose()
  82. {
  83. Memory.Unmanaged.Free(Ptr, Allocator);
  84. Ptr = null;
  85. Keys = null;
  86. Next = null;
  87. Buckets = null;
  88. Count = 0;
  89. BucketCapacity = 0;
  90. }
  91. internal static HashMapHelper<TKey>* Alloc(int capacity, int sizeOfValueT, int minGrowth, AllocatorManager.AllocatorHandle allocator)
  92. {
  93. var data = (HashMapHelper<TKey>*)Memory.Unmanaged.Allocate(sizeof(HashMapHelper<TKey>), UnsafeUtility.AlignOf<HashMapHelper<TKey>>(), allocator);
  94. data->Init(capacity, sizeOfValueT, minGrowth, allocator);
  95. return data;
  96. }
  97. internal static void Free(HashMapHelper<TKey>* data)
  98. {
  99. if (data == null)
  100. {
  101. throw new InvalidOperationException("Hash based container has yet to be created or has been destroyed!");
  102. }
  103. data->Dispose();
  104. Memory.Unmanaged.Free(data, data->Allocator);
  105. }
  106. internal void Resize(int newCapacity)
  107. {
  108. newCapacity = math.max(newCapacity, Count);
  109. var newBucketCapacity = math.ceilpow2(GetBucketSize(newCapacity));
  110. if (Capacity == newCapacity && BucketCapacity == newBucketCapacity)
  111. {
  112. return;
  113. }
  114. ResizeExact(newCapacity, newBucketCapacity);
  115. }
  116. internal void ResizeExact(int newCapacity, int newBucketCapacity)
  117. {
  118. int keyOffset, nextOffset, bucketOffset;
  119. int totalSize = CalculateDataSize(newCapacity, newBucketCapacity, SizeOfTValue, out keyOffset, out nextOffset, out bucketOffset);
  120. var oldPtr = Ptr;
  121. var oldKeys = Keys;
  122. var oldNext = Next;
  123. var oldBuckets = Buckets;
  124. var oldBucketCapacity = BucketCapacity;
  125. Ptr = (byte*)Memory.Unmanaged.Allocate(totalSize, JobsUtility.CacheLineSize, Allocator);
  126. Keys = (TKey*)(Ptr + keyOffset);
  127. Next = (int*)(Ptr + nextOffset);
  128. Buckets = (int*)(Ptr + bucketOffset);
  129. Capacity = newCapacity;
  130. BucketCapacity = newBucketCapacity;
  131. Clear();
  132. for (int i = 0, num = oldBucketCapacity; i < num; ++i)
  133. {
  134. for (int idx = oldBuckets[i]; idx != -1; idx = oldNext[idx])
  135. {
  136. var newIdx = TryAdd(oldKeys[idx]);
  137. UnsafeUtility.MemCpy(Ptr + SizeOfTValue * newIdx, oldPtr + SizeOfTValue * idx, SizeOfTValue);
  138. }
  139. }
  140. Memory.Unmanaged.Free(oldPtr, Allocator);
  141. }
  142. internal void TrimExcess()
  143. {
  144. var capacity = CalcCapacityCeilPow2(Count);
  145. ResizeExact(capacity, GetBucketSize(capacity));
  146. }
  147. internal static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
  148. {
  149. var sizeOfTKey = sizeof(TKey);
  150. var sizeOfInt = sizeof(int);
  151. var valuesSize = sizeOfTValue * capacity;
  152. var keysSize = sizeOfTKey * capacity;
  153. var nextSize = sizeOfInt * capacity;
  154. var bucketSize = sizeOfInt * bucketCapacity;
  155. var totalSize = valuesSize + keysSize + nextSize + bucketSize;
  156. outKeyOffset = 0 + valuesSize;
  157. outNextOffset = outKeyOffset + keysSize;
  158. outBucketOffset = outNextOffset + nextSize;
  159. return totalSize;
  160. }
  161. internal readonly int GetCount()
  162. {
  163. if (AllocatedIndex <= 0)
  164. {
  165. return 0;
  166. }
  167. var numFree = 0;
  168. for (var freeIdx = FirstFreeIdx; freeIdx >= 0; freeIdx = Next[freeIdx])
  169. {
  170. ++numFree;
  171. }
  172. return math.min(Capacity, AllocatedIndex) - numFree;
  173. }
  174. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  175. int GetBucket(in TKey key)
  176. {
  177. return (int)((uint)key.GetHashCode() & (BucketCapacity - 1));
  178. }
  179. internal int TryAdd(in TKey key)
  180. {
  181. if (-1 == Find(key))
  182. {
  183. // Allocate an entry from the free list
  184. int idx;
  185. int* next;
  186. if (AllocatedIndex >= Capacity && FirstFreeIdx < 0)
  187. {
  188. int newCap = CalcCapacityCeilPow2(Capacity + (1 << Log2MinGrowth));
  189. Resize(newCap);
  190. }
  191. idx = FirstFreeIdx;
  192. if (idx >= 0)
  193. {
  194. FirstFreeIdx = Next[idx];
  195. }
  196. else
  197. {
  198. idx = AllocatedIndex++;
  199. }
  200. CheckIndexOutOfBounds(idx);
  201. UnsafeUtility.WriteArrayElement(Keys, idx, key);
  202. var bucket = GetBucket(key);
  203. // Add the index to the hash-map
  204. next = Next;
  205. next[idx] = Buckets[bucket];
  206. Buckets[bucket] = idx;
  207. Count++;
  208. return idx;
  209. }
  210. return -1;
  211. }
  212. internal int Find(TKey key)
  213. {
  214. if (AllocatedIndex > 0)
  215. {
  216. // First find the slot based on the hash
  217. var bucket = GetBucket(key);
  218. var entryIdx = Buckets[bucket];
  219. if ((uint)entryIdx < (uint)Capacity)
  220. {
  221. var nextPtrs = Next;
  222. while (!UnsafeUtility.ReadArrayElement<TKey>(Keys, entryIdx).Equals(key))
  223. {
  224. entryIdx = nextPtrs[entryIdx];
  225. if ((uint)entryIdx >= (uint)Capacity)
  226. {
  227. return -1;
  228. }
  229. }
  230. return entryIdx;
  231. }
  232. }
  233. return -1;
  234. }
  235. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  236. internal bool TryGetValue<TValue>(TKey key, out TValue item)
  237. where TValue : unmanaged
  238. {
  239. var idx = Find(key);
  240. if (-1 != idx)
  241. {
  242. item = UnsafeUtility.ReadArrayElement<TValue>(Ptr, idx);
  243. return true;
  244. }
  245. item = default;
  246. return false;
  247. }
  248. internal int TryRemove(TKey key)
  249. {
  250. if (Capacity != 0)
  251. {
  252. var removed = 0;
  253. // First find the slot based on the hash
  254. var bucket = GetBucket(key);
  255. var prevEntry = -1;
  256. var entryIdx = Buckets[bucket];
  257. while (entryIdx >= 0 && entryIdx < Capacity)
  258. {
  259. if (UnsafeUtility.ReadArrayElement<TKey>(Keys, entryIdx).Equals(key))
  260. {
  261. ++removed;
  262. // Found matching element, remove it
  263. if (prevEntry < 0)
  264. {
  265. Buckets[bucket] = Next[entryIdx];
  266. }
  267. else
  268. {
  269. Next[prevEntry] = Next[entryIdx];
  270. }
  271. // And free the index
  272. int nextIdx = Next[entryIdx];
  273. Next[entryIdx] = FirstFreeIdx;
  274. FirstFreeIdx = entryIdx;
  275. entryIdx = nextIdx;
  276. break;
  277. }
  278. else
  279. {
  280. prevEntry = entryIdx;
  281. entryIdx = Next[entryIdx];
  282. }
  283. }
  284. Count -= removed;
  285. return 0 != removed ? removed : -1;
  286. }
  287. return -1;
  288. }
  289. internal bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
  290. {
  291. for (int i = bucketIndex, num = BucketCapacity; i < num; ++i)
  292. {
  293. var idx = Buckets[i];
  294. if (idx != -1)
  295. {
  296. index = idx;
  297. bucketIndex = i + 1;
  298. nextIndex = Next[idx];
  299. return true;
  300. }
  301. }
  302. index = -1;
  303. bucketIndex = BucketCapacity;
  304. nextIndex = -1;
  305. return false;
  306. }
  307. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  308. internal bool MoveNext(ref int bucketIndex, ref int nextIndex, out int index)
  309. {
  310. if (nextIndex != -1)
  311. {
  312. index = nextIndex;
  313. nextIndex = Next[nextIndex];
  314. return true;
  315. }
  316. return MoveNextSearch(ref bucketIndex, ref nextIndex, out index);
  317. }
  318. internal NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator)
  319. {
  320. var result = CollectionHelper.CreateNativeArray<TKey>(Count, allocator, NativeArrayOptions.UninitializedMemory);
  321. for (int i = 0, count = 0, max = result.Length, capacity = BucketCapacity
  322. ; i < capacity && count < max
  323. ; ++i
  324. )
  325. {
  326. int bucket = Buckets[i];
  327. while (bucket != -1)
  328. {
  329. result[count++] = UnsafeUtility.ReadArrayElement<TKey>(Keys, bucket);
  330. bucket = Next[bucket];
  331. }
  332. }
  333. return result;
  334. }
  335. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  336. internal NativeArray<TValue> GetValueArray<TValue>(AllocatorManager.AllocatorHandle allocator)
  337. where TValue : unmanaged
  338. {
  339. var result = CollectionHelper.CreateNativeArray<TValue>(Count, allocator, NativeArrayOptions.UninitializedMemory);
  340. for (int i = 0, count = 0, max = result.Length, capacity = BucketCapacity
  341. ; i < capacity && count < max
  342. ; ++i
  343. )
  344. {
  345. int bucket = Buckets[i];
  346. while (bucket != -1)
  347. {
  348. result[count++] = UnsafeUtility.ReadArrayElement<TValue>(Ptr, bucket);
  349. bucket = Next[bucket];
  350. }
  351. }
  352. return result;
  353. }
  354. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  355. internal NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays<TValue>(AllocatorManager.AllocatorHandle allocator)
  356. where TValue : unmanaged
  357. {
  358. var result = new NativeKeyValueArrays<TKey, TValue>(Count, allocator, NativeArrayOptions.UninitializedMemory);
  359. for (int i = 0, count = 0, max = result.Length, capacity = BucketCapacity
  360. ; i < capacity && count < max
  361. ; ++i
  362. )
  363. {
  364. int bucket = Buckets[i];
  365. while (bucket != -1)
  366. {
  367. result.Keys[count] = UnsafeUtility.ReadArrayElement<TKey>(Keys, bucket);
  368. result.Values[count] = UnsafeUtility.ReadArrayElement<TValue>(Ptr, bucket);
  369. count++;
  370. bucket = Next[bucket];
  371. }
  372. }
  373. return result;
  374. }
  375. internal unsafe struct Enumerator
  376. {
  377. [NativeDisableUnsafePtrRestriction]
  378. internal HashMapHelper<TKey>* m_Data;
  379. internal int m_Index;
  380. internal int m_BucketIndex;
  381. internal int m_NextIndex;
  382. internal unsafe Enumerator(HashMapHelper<TKey>* data)
  383. {
  384. m_Data = data;
  385. m_Index = -1;
  386. m_BucketIndex = 0;
  387. m_NextIndex = -1;
  388. }
  389. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  390. internal bool MoveNext()
  391. {
  392. return m_Data->MoveNext(ref m_BucketIndex, ref m_NextIndex, out m_Index);
  393. }
  394. internal void Reset()
  395. {
  396. m_Index = -1;
  397. m_BucketIndex = 0;
  398. m_NextIndex = -1;
  399. }
  400. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  401. internal KVPair<TKey, TValue> GetCurrent<TValue>()
  402. where TValue : unmanaged
  403. {
  404. return new KVPair<TKey, TValue> { m_Data = m_Data, m_Index = m_Index };
  405. }
  406. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  407. internal TKey GetCurrentKey()
  408. {
  409. if (m_Index != -1)
  410. {
  411. return m_Data->Keys[m_Index];
  412. }
  413. return default;
  414. }
  415. }
  416. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  417. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  418. void CheckIndexOutOfBounds(int idx)
  419. {
  420. if ((uint)idx >= (uint)Capacity)
  421. {
  422. throw new InvalidOperationException($"Internal HashMap error. idx {idx}");
  423. }
  424. }
  425. }
  426. /// <summary>
  427. /// An unordered, expandable associative array.
  428. /// </summary>
  429. /// <typeparam name="TKey">The type of the keys.</typeparam>
  430. /// <typeparam name="TValue">The type of the values.</typeparam>
  431. [StructLayout(LayoutKind.Sequential)]
  432. [DebuggerTypeProxy(typeof(UnsafeHashMapDebuggerTypeProxy<,>))]
  433. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
  434. public unsafe struct UnsafeHashMap<TKey, TValue>
  435. : INativeDisposable
  436. , IEnumerable<KVPair<TKey, TValue>> // Used by collection initializers.
  437. where TKey : unmanaged, IEquatable<TKey>
  438. where TValue : unmanaged
  439. {
  440. [NativeDisableUnsafePtrRestriction]
  441. internal HashMapHelper<TKey> m_Data;
  442. /// <summary>
  443. /// Initializes and returns an instance of UnsafeHashMap.
  444. /// </summary>
  445. /// <param name="initialCapacity">The number of key-value pairs that should fit in the initial allocation.</param>
  446. /// <param name="allocator">The allocator to use.</param>
  447. public UnsafeHashMap(int initialCapacity, AllocatorManager.AllocatorHandle allocator)
  448. {
  449. m_Data = default;
  450. m_Data.Init(initialCapacity, sizeof(TValue), HashMapHelper<TKey>.kMinimumCapacity, allocator);
  451. }
  452. /// <summary>
  453. /// Releases all resources (memory).
  454. /// </summary>
  455. public void Dispose()
  456. {
  457. if (!IsCreated)
  458. {
  459. return;
  460. }
  461. m_Data.Dispose();
  462. }
  463. /// <summary>
  464. /// Creates and schedules a job that will dispose this hash map.
  465. /// </summary>
  466. /// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
  467. /// <returns>The handle of a new job that will dispose this hash map.</returns>
  468. public JobHandle Dispose(JobHandle inputDeps)
  469. {
  470. if (!IsCreated)
  471. {
  472. return inputDeps;
  473. }
  474. var jobHandle = new UnsafeDisposeJob { Ptr = m_Data.Ptr, Allocator = m_Data.Allocator }.Schedule(inputDeps);
  475. m_Data = default;
  476. return jobHandle;
  477. }
  478. /// <summary>
  479. /// Whether this hash map has been allocated (and not yet deallocated).
  480. /// </summary>
  481. /// <value>True if this hash map has been allocated (and not yet deallocated).</value>
  482. public readonly bool IsCreated
  483. {
  484. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  485. get => m_Data.IsCreated;
  486. }
  487. /// <summary>
  488. /// Whether this hash map is empty.
  489. /// </summary>
  490. /// <value>True if this hash map is empty or if the map has not been constructed.</value>
  491. public readonly bool IsEmpty
  492. {
  493. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  494. get => m_Data.IsEmpty;
  495. }
  496. /// <summary>
  497. /// The current number of key-value pairs in this hash map.
  498. /// </summary>
  499. /// <returns>The current number of key-value pairs in this hash map.</returns>
  500. public readonly int Count
  501. {
  502. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  503. get => m_Data.Count;
  504. }
  505. /// <summary>
  506. /// The number of key-value pairs that fit in the current allocation.
  507. /// </summary>
  508. /// <value>The number of key-value pairs that fit in the current allocation.</value>
  509. /// <param name="value">A new capacity. Must be larger than the current capacity.</param>
  510. public int Capacity
  511. {
  512. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  513. readonly get => m_Data.Capacity;
  514. set => m_Data.Resize(value);
  515. }
  516. /// <summary>
  517. /// Removes all key-value pairs.
  518. /// </summary>
  519. /// <remarks>Does not change the capacity.</remarks>
  520. public void Clear()
  521. {
  522. m_Data.Clear();
  523. }
  524. /// <summary>
  525. /// Adds a new key-value pair.
  526. /// </summary>
  527. /// <remarks>If the key is already present, this method returns false without modifying the hash map.</remarks>
  528. /// <param name="key">The key to add.</param>
  529. /// <param name="item">The value to add.</param>
  530. /// <returns>True if the key-value pair was added.</returns>
  531. public bool TryAdd(TKey key, TValue item)
  532. {
  533. var idx = m_Data.TryAdd(key);
  534. if (-1 != idx)
  535. {
  536. UnsafeUtility.WriteArrayElement(m_Data.Ptr, idx, item);
  537. return true;
  538. }
  539. return false;
  540. }
  541. /// <summary>
  542. /// Adds a new key-value pair.
  543. /// </summary>
  544. /// <remarks>If the key is already present, this method throws without modifying the hash map.</remarks>
  545. /// <param name="key">The key to add.</param>
  546. /// <param name="item">The value to add.</param>
  547. /// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
  548. public void Add(TKey key, TValue item)
  549. {
  550. var result = TryAdd(key, item);
  551. if (!result)
  552. {
  553. ThrowKeyAlreadyAdded(key);
  554. }
  555. }
  556. /// <summary>
  557. /// Removes a key-value pair.
  558. /// </summary>
  559. /// <param name="key">The key to remove.</param>
  560. /// <returns>True if a key-value pair was removed.</returns>
  561. public bool Remove(TKey key)
  562. {
  563. return -1 != m_Data.TryRemove(key);
  564. }
  565. /// <summary>
  566. /// Returns the value associated with a key.
  567. /// </summary>
  568. /// <param name="key">The key to look up.</param>
  569. /// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
  570. /// <returns>True if the key was present.</returns>
  571. public bool TryGetValue(TKey key, out TValue item)
  572. {
  573. return m_Data.TryGetValue(key, out item);
  574. }
  575. /// <summary>
  576. /// Returns true if a given key is present in this hash map.
  577. /// </summary>
  578. /// <param name="key">The key to look up.</param>
  579. /// <returns>True if the key was present.</returns>
  580. public bool ContainsKey(TKey key)
  581. {
  582. return -1 != m_Data.Find(key);
  583. }
  584. /// <summary>
  585. /// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
  586. /// </summary>
  587. public void TrimExcess() => m_Data.TrimExcess();
  588. /// <summary>
  589. /// Gets and sets values by key.
  590. /// </summary>
  591. /// <remarks>Getting a key that is not present will throw. Setting a key that is not already present will add the key.</remarks>
  592. /// <param name="key">The key to look up.</param>
  593. /// <value>The value associated with the key.</value>
  594. /// <exception cref="ArgumentException">For getting, thrown if the key was not present.</exception>
  595. public TValue this[TKey key]
  596. {
  597. get
  598. {
  599. TValue result;
  600. if (!m_Data.TryGetValue(key, out result))
  601. {
  602. ThrowKeyNotPresent(key);
  603. }
  604. return result;
  605. }
  606. set
  607. {
  608. var idx = m_Data.Find(key);
  609. if (-1 != idx)
  610. {
  611. UnsafeUtility.WriteArrayElement(m_Data.Ptr, idx, value);
  612. return;
  613. }
  614. TryAdd(key, value);
  615. }
  616. }
  617. /// <summary>
  618. /// Returns an array with a copy of all this hash map's keys (in no particular order).
  619. /// </summary>
  620. /// <param name="allocator">The allocator to use.</param>
  621. /// <returns>An array with a copy of all this hash map's keys (in no particular order).</returns>
  622. public NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator) => m_Data.GetKeyArray(allocator);
  623. /// <summary>
  624. /// Returns an array with a copy of all this hash map's values (in no particular order).
  625. /// </summary>
  626. /// <param name="allocator">The allocator to use.</param>
  627. /// <returns>An array with a copy of all this hash map's values (in no particular order).</returns>
  628. public NativeArray<TValue> GetValueArray(AllocatorManager.AllocatorHandle allocator) => m_Data.GetValueArray<TValue>(allocator);
  629. /// <summary>
  630. /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values.
  631. /// </summary>
  632. /// <remarks>The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`.</remarks>
  633. /// <param name="allocator">The allocator to use.</param>
  634. /// <returns>A NativeKeyValueArrays with a copy of all this hash map's keys and values.</returns>
  635. public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) => m_Data.GetKeyValueArrays<TValue>(allocator);
  636. /// <summary>
  637. /// Returns an enumerator over the key-value pairs of this hash map.
  638. /// </summary>
  639. /// <returns>An enumerator over the key-value pairs of this hash map.</returns>
  640. public Enumerator GetEnumerator()
  641. {
  642. // return new Enumerator { Data = m_Data, Index = -1 };
  643. fixed (HashMapHelper<TKey>* data = &m_Data)
  644. {
  645. return new Enumerator { m_Enumerator = new HashMapHelper<TKey>.Enumerator(data) };
  646. }
  647. }
  648. /// <summary>
  649. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  650. /// </summary>
  651. /// <returns>Throws NotImplementedException.</returns>
  652. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  653. IEnumerator<KVPair<TKey, TValue>> IEnumerable<KVPair<TKey, TValue>>.GetEnumerator()
  654. {
  655. throw new NotImplementedException();
  656. }
  657. /// <summary>
  658. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  659. /// </summary>
  660. /// <returns>Throws NotImplementedException.</returns>
  661. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  662. IEnumerator IEnumerable.GetEnumerator()
  663. {
  664. throw new NotImplementedException();
  665. }
  666. /// <summary>
  667. /// An enumerator over the key-value pairs of a container.
  668. /// </summary>
  669. /// <remarks>
  670. /// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
  671. /// From this state, the first <see cref="MoveNext"/> call advances the enumerator to the first key-value pair.
  672. /// </remarks>
  673. public struct Enumerator : IEnumerator<KVPair<TKey, TValue>>
  674. {
  675. internal HashMapHelper<TKey>.Enumerator m_Enumerator;
  676. /// <summary>
  677. /// Does nothing.
  678. /// </summary>
  679. public void Dispose() { }
  680. /// <summary>
  681. /// Advances the enumerator to the next key-value pair.
  682. /// </summary>
  683. /// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
  684. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  685. public bool MoveNext() => m_Enumerator.MoveNext();
  686. /// <summary>
  687. /// Resets the enumerator to its initial state.
  688. /// </summary>
  689. public void Reset() => m_Enumerator.Reset();
  690. /// <summary>
  691. /// The current key-value pair.
  692. /// </summary>
  693. /// <value>The current key-value pair.</value>
  694. public KVPair<TKey, TValue> Current
  695. {
  696. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  697. get => m_Enumerator.GetCurrent<TValue>();
  698. }
  699. /// <summary>
  700. /// Gets the element at the current position of the enumerator in the container.
  701. /// </summary>
  702. object IEnumerator.Current => Current;
  703. }
  704. /// <summary>
  705. /// Returns a readonly version of this UnsafeHashMap instance.
  706. /// </summary>
  707. /// <remarks>ReadOnly containers point to the same underlying data as the UnsafeHashMap it is made from.</remarks>
  708. /// <returns>ReadOnly instance for this.</returns>
  709. public ReadOnly AsReadOnly()
  710. {
  711. return new ReadOnly(ref m_Data);
  712. }
  713. /// <summary>
  714. /// A read-only alias for the value of a UnsafeHashMap. Does not have its own allocated storage.
  715. /// </summary>
  716. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
  717. public struct ReadOnly
  718. : IEnumerable<KVPair<TKey, TValue>>
  719. {
  720. [NativeDisableUnsafePtrRestriction]
  721. internal HashMapHelper<TKey> m_Data;
  722. internal ReadOnly(ref HashMapHelper<TKey> data)
  723. {
  724. m_Data = data;
  725. }
  726. /// <summary>
  727. /// Whether this hash map has been allocated (and not yet deallocated).
  728. /// </summary>
  729. /// <value>True if this hash map has been allocated (and not yet deallocated).</value>
  730. public readonly bool IsCreated
  731. {
  732. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  733. get => m_Data.IsCreated;
  734. }
  735. /// <summary>
  736. /// Whether this hash map is empty.
  737. /// </summary>
  738. /// <value>True if this hash map is empty or if the map has not been constructed.</value>
  739. public readonly bool IsEmpty
  740. {
  741. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  742. get => m_Data.IsEmpty;
  743. }
  744. /// <summary>
  745. /// The current number of key-value pairs in this hash map.
  746. /// </summary>
  747. /// <returns>The current number of key-value pairs in this hash map.</returns>
  748. public readonly int Count
  749. {
  750. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  751. get => m_Data.Count;
  752. }
  753. /// <summary>
  754. /// The number of key-value pairs that fit in the current allocation.
  755. /// </summary>
  756. /// <value>The number of key-value pairs that fit in the current allocation.</value>
  757. public readonly int Capacity
  758. {
  759. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  760. get => m_Data.Capacity;
  761. }
  762. /// <summary>
  763. /// Returns the value associated with a key.
  764. /// </summary>
  765. /// <param name="key">The key to look up.</param>
  766. /// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
  767. /// <returns>True if the key was present.</returns>
  768. public readonly bool TryGetValue(TKey key, out TValue item) => m_Data.TryGetValue(key, out item);
  769. /// <summary>
  770. /// Returns true if a given key is present in this hash map.
  771. /// </summary>
  772. /// <param name="key">The key to look up.</param>
  773. /// <returns>True if the key was present.</returns>
  774. public readonly bool ContainsKey(TKey key)
  775. {
  776. return -1 != m_Data.Find(key);
  777. }
  778. /// <summary>
  779. /// Gets values by key.
  780. /// </summary>
  781. /// <remarks>Getting a key that is not present will throw.</remarks>
  782. /// <param name="key">The key to look up.</param>
  783. /// <value>The value associated with the key.</value>
  784. /// <exception cref="ArgumentException">For getting, thrown if the key was not present.</exception>
  785. public readonly TValue this[TKey key]
  786. {
  787. get
  788. {
  789. TValue result;
  790. m_Data.TryGetValue(key, out result);
  791. return result;
  792. }
  793. }
  794. /// <summary>
  795. /// Returns an array with a copy of all this hash map's keys (in no particular order).
  796. /// </summary>
  797. /// <param name="allocator">The allocator to use.</param>
  798. /// <returns>An array with a copy of all this hash map's keys (in no particular order).</returns>
  799. public readonly NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator) => m_Data.GetKeyArray(allocator);
  800. /// <summary>
  801. /// Returns an array with a copy of all this hash map's values (in no particular order).
  802. /// </summary>
  803. /// <param name="allocator">The allocator to use.</param>
  804. /// <returns>An array with a copy of all this hash map's values (in no particular order).</returns>
  805. public readonly NativeArray<TValue> GetValueArray(AllocatorManager.AllocatorHandle allocator) => m_Data.GetValueArray<TValue>(allocator);
  806. /// <summary>
  807. /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values.
  808. /// </summary>
  809. /// <remarks>The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`.</remarks>
  810. /// <param name="allocator">The allocator to use.</param>
  811. /// <returns>A NativeKeyValueArrays with a copy of all this hash map's keys and values.</returns>
  812. public readonly NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) => m_Data.GetKeyValueArrays<TValue>(allocator);
  813. /// <summary>
  814. /// Returns an enumerator over the key-value pairs of this hash map.
  815. /// </summary>
  816. /// <returns>An enumerator over the key-value pairs of this hash map.</returns>
  817. public readonly Enumerator GetEnumerator()
  818. {
  819. fixed (HashMapHelper<TKey>* data = &m_Data)
  820. {
  821. return new Enumerator { m_Enumerator = new HashMapHelper<TKey>.Enumerator(data) };
  822. }
  823. }
  824. /// <summary>
  825. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  826. /// </summary>
  827. /// <returns>Throws NotImplementedException.</returns>
  828. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  829. IEnumerator<KVPair<TKey, TValue>> IEnumerable<KVPair<TKey, TValue>>.GetEnumerator()
  830. {
  831. throw new NotImplementedException();
  832. }
  833. /// <summary>
  834. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  835. /// </summary>
  836. /// <returns>Throws NotImplementedException.</returns>
  837. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  838. IEnumerator IEnumerable.GetEnumerator()
  839. {
  840. throw new NotImplementedException();
  841. }
  842. }
  843. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  844. void ThrowKeyNotPresent(TKey key)
  845. {
  846. throw new ArgumentException($"Key: {key} is not present.");
  847. }
  848. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  849. void ThrowKeyAlreadyAdded(TKey key)
  850. {
  851. throw new ArgumentException($"An item with the same key has already been added: {key}");
  852. }
  853. }
  854. internal sealed class UnsafeHashMapDebuggerTypeProxy<TKey, TValue>
  855. where TKey : unmanaged, IEquatable<TKey>
  856. where TValue : unmanaged
  857. {
  858. HashMapHelper<TKey> Data;
  859. public UnsafeHashMapDebuggerTypeProxy(UnsafeHashMap<TKey, TValue> target)
  860. {
  861. Data = target.m_Data;
  862. }
  863. public UnsafeHashMapDebuggerTypeProxy(UnsafeHashMap<TKey, TValue>.ReadOnly target)
  864. {
  865. Data = target.m_Data;
  866. }
  867. public List<Pair<TKey, TValue>> Items
  868. {
  869. get
  870. {
  871. var result = new List<Pair<TKey, TValue>>();
  872. using (var kva = Data.GetKeyValueArrays<TValue>(Allocator.Temp))
  873. {
  874. for (var i = 0; i < kva.Length; ++i)
  875. {
  876. result.Add(new Pair<TKey, TValue>(kva.Keys[i], kva.Values[i]));
  877. }
  878. }
  879. return result;
  880. }
  881. }
  882. }
  883. }