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