A game about forced loneliness, made by TACStudios
at master 1034 lines 38 kB view raw
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}