A game about forced loneliness, made by TACStudios
at master 978 lines 31 kB view raw
1using System; 2using System.Runtime.InteropServices; 3using NUnit.Framework; 4using Unity.Burst; 5using Unity.Collections; 6using Unity.Collections.Tests; 7using Unity.Jobs; 8using UnityEngine; 9using UnityEngine.TestTools; 10using Unity.Collections.LowLevel.Unsafe; 11using System.Text.RegularExpressions; 12using Assert = FastAssert; 13 14internal class NativeParallelHashMapTests : CollectionsTestFixture 15{ 16#pragma warning disable 0649 // always default value 17 18 struct NonBlittableStruct : IEquatable<NonBlittableStruct> 19 { 20 object o; 21 22 public bool Equals(NonBlittableStruct other) 23 { 24 return Equals(o, other.o); 25 } 26 27 public override bool Equals(object obj) 28 { 29 if (ReferenceEquals(null, obj)) return false; 30 return obj is NonBlittableStruct other && Equals(other); 31 } 32 33 public override int GetHashCode() 34 { 35 return (o != null ? o.GetHashCode() : 0); 36 } 37 } 38 39#pragma warning restore 0649 40 41 static void ExpectedCount<TKey, TValue>(ref NativeParallelHashMap<TKey, TValue> container, int expected) 42 where TKey : unmanaged, IEquatable<TKey> 43 where TValue : unmanaged 44 { 45 Assert.AreEqual(expected == 0, container.IsEmpty); 46 Assert.AreEqual(expected, container.Count()); 47 } 48 49 [Test] 50 public void NativeParallelHashMap_TryAdd_TryGetValue_Clear() 51 { 52 var hashMap = new NativeParallelHashMap<int, int>(16, Allocator.Temp); 53 ExpectedCount(ref hashMap, 0); 54 55 int iSquared; 56 // Make sure GetValue fails if hash map is empty 57 Assert.IsFalse(hashMap.TryGetValue(0, out iSquared), "TryGetValue on empty hash map did not fail"); 58 59 // Make sure inserting values work 60 for (int i = 0; i < 16; ++i) 61 Assert.IsTrue(hashMap.TryAdd(i, i * i), "Failed to add value"); 62 ExpectedCount(ref hashMap, 16); 63 64 // Make sure inserting duplicate keys fails 65 for (int i = 0; i < 16; ++i) 66 Assert.IsFalse(hashMap.TryAdd(i, i), "Adding duplicate keys did not fail"); 67 ExpectedCount(ref hashMap, 16); 68 69 // Make sure reading the inserted values work 70 for (int i = 0; i < 16; ++i) 71 { 72 Assert.IsTrue(hashMap.TryGetValue(i, out iSquared), "Failed get value from hash table"); 73 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table"); 74 } 75 76 // Make sure clearing removes all keys 77 hashMap.Clear(); 78 ExpectedCount(ref hashMap, 0); 79 80 for (int i = 0; i < 16; ++i) 81 Assert.IsFalse(hashMap.TryGetValue(i, out iSquared), "Got value from hash table after clearing"); 82 83 hashMap.Dispose(); 84 } 85 86 [Test] 87 [TestRequiresCollectionChecks] 88 public void NativeParallelHashMap_Full_HashMap_Throws() 89 { 90 var hashMap = new NativeParallelHashMap<int, int>(16, Allocator.Temp); 91 // Fill the hash map 92 for (int i = 0; i < 16; ++i) 93 Assert.IsTrue(hashMap.TryAdd(i, i), "Failed to add value"); 94 // Make sure overallocating throws and exception if using the Concurrent version - normal hash map would grow 95 var cHashMap = hashMap.AsParallelWriter(); 96 Assert.Throws<System.InvalidOperationException>(() => { cHashMap.TryAdd(100, 100); }); 97 hashMap.Dispose(); 98 } 99 100 [Test] 101 public void NativeParallelHashMap_Key_Collisions() 102 { 103 var hashMap = new NativeParallelHashMap<int, int>(16, Allocator.Temp); 104 int iSquared; 105 // Make sure GetValue fails if hash map is empty 106 Assert.IsFalse(hashMap.TryGetValue(0, out iSquared), "TryGetValue on empty hash map did not fail"); 107 // Make sure inserting values work 108 for (int i = 0; i < 8; ++i) 109 Assert.IsTrue(hashMap.TryAdd(i, i * i), "Failed to add value"); 110 // The bucket size is capacity * 2, adding that number should result in hash collisions 111 for (int i = 0; i < 8; ++i) 112 Assert.IsTrue(hashMap.TryAdd(i + 32, i), "Failed to add value with potential hash collision"); 113 // Make sure reading the inserted values work 114 for (int i = 0; i < 8; ++i) 115 { 116 Assert.IsTrue(hashMap.TryGetValue(i, out iSquared), "Failed get value from hash table"); 117 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table"); 118 } 119 for (int i = 0; i < 8; ++i) 120 { 121 Assert.IsTrue(hashMap.TryGetValue(i + 32, out iSquared), "Failed get value from hash table"); 122 Assert.AreEqual(iSquared, i, "Got the wrong value from the hash table"); 123 } 124 hashMap.Dispose(); 125 } 126 127 [StructLayout(LayoutKind.Explicit)] 128 unsafe struct LargeKey : IEquatable<LargeKey> 129 { 130 [FieldOffset(0)] 131 public ulong Ptr; 132 133 [FieldOffset(300)] 134 int x; 135 136 public bool Equals(LargeKey rhs) 137 { 138 return Ptr == rhs.Ptr; 139 } 140 public override int GetHashCode() 141 { 142 return ((int)(Ptr>>32) * 1327) ^ (int)(Ptr & 0x7FFFFFFF); 143 } 144 } 145 146 [BurstCompile] 147 struct HashMapTryAddAtomic : IJobParallelFor 148 { 149 [ReadOnly] 150 public NativeArray<LargeKey> keys; 151 152 [WriteOnly] 153 public NativeParallelHashMap<LargeKey, bool>.ParallelWriter hashMap; 154 155 public void Execute(int index) 156 { 157 hashMap.TryAdd(keys[index & (keys.Length - 1)], false); 158 } 159 } 160 161 [Test] 162 public unsafe void NativeParallelHashMap_Key_Collisions_FromJobs() 163 { 164 var keys = CollectionHelper.CreateNativeArray<LargeKey>(4, CommonRwdAllocator.Handle); 165 for (var i = 0; i < keys.Length; i++) 166 { 167 keys[i] = new LargeKey { Ptr = (((ulong)i) << 32) }; 168 } 169 170 for (var spin = 0; spin < 1024; spin++) 171 { 172 var hashMap = new NativeParallelHashMap<LargeKey, bool>(32, CommonRwdAllocator.Handle); 173 174 var jobHandle = new HashMapTryAddAtomic 175 { 176 keys = keys, 177 hashMap = hashMap.AsParallelWriter(), 178 } 179 .Schedule(8, 1); 180 181 jobHandle.Complete(); 182 183 Assert.AreEqual(keys.Length, hashMap.Count()); 184 185 for (var i = 0; i < keys.Length; ++i) 186 { 187 var key = new LargeKey { Ptr = (((ulong)i) << 32) }; 188 Assert.IsTrue(hashMap.ContainsKey(key)); 189 } 190 191 hashMap.Dispose(); 192 } 193 194 keys.Dispose(); 195 } 196 197 [Test] 198 public void NativeParallelHashMap_HashMapSupportsAutomaticCapacityChange() 199 { 200 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Temp); 201 int iSquared; 202 // Make sure inserting values work and grows the capacity 203 for (int i = 0; i < 8; ++i) 204 Assert.IsTrue(hashMap.TryAdd(i, i * i), "Failed to add value"); 205 Assert.IsTrue(hashMap.Capacity >= 8, "Capacity was not updated correctly"); 206 // Make sure reading the inserted values work 207 for (int i = 0; i < 8; ++i) 208 { 209 Assert.IsTrue(hashMap.TryGetValue(i, out iSquared), "Failed get value from hash table"); 210 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table"); 211 } 212 hashMap.Dispose(); 213 } 214 215 [Test] 216 public void NativeParallelHashMap_HashMapSameKey() 217 { 218 using (var hashMap = new NativeParallelHashMap<int, int>(0, Allocator.Persistent)) 219 { 220 Assert.DoesNotThrow(() => hashMap.Add(0, 0)); 221#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 222 Assert.Throws<ArgumentException>(() => hashMap.Add(0, 0)); 223#endif 224 } 225 226 using (var hashMap = new NativeParallelHashMap<int, int>(0, Allocator.Persistent)) 227 { 228 Assert.IsTrue(hashMap.TryAdd(0, 0)); 229 Assert.IsFalse(hashMap.TryAdd(0, 0)); 230 } 231 } 232 233 [Test] 234 public void NativeParallelHashMap_IsEmpty() 235 { 236 var container = new NativeParallelHashMap<int, int>(0, Allocator.Persistent); 237 Assert.IsTrue(container.IsEmpty); 238 239 container.TryAdd(0, 0); 240 Assert.IsFalse(container.IsEmpty); 241 Assert.AreEqual(1, container.Capacity); 242 ExpectedCount(ref container, 1); 243 244 container.Remove(0); 245 Assert.IsTrue(container.IsEmpty); 246 247 container.TryAdd(0, 0); 248 container.Clear(); 249 Assert.IsTrue(container.IsEmpty); 250 251 container.Dispose(); 252 } 253 254 [Test] 255 public void NativeParallelHashMap_HashMapEmptyCapacity() 256 { 257 var hashMap = new NativeParallelHashMap<int, int>(0, Allocator.Persistent); 258 hashMap.TryAdd(0, 0); 259 Assert.AreEqual(1, hashMap.Capacity); 260 ExpectedCount(ref hashMap, 1); 261 hashMap.Dispose(); 262 } 263 264 [Test] 265 public void NativeParallelHashMap_Remove() 266 { 267 var hashMap = new NativeParallelHashMap<int, int>(8, Allocator.Temp); 268 int iSquared; 269 // Make sure inserting values work 270 for (int i = 0; i < 8; ++i) 271 Assert.IsTrue(hashMap.TryAdd(i, i * i), "Failed to add value"); 272 Assert.AreEqual(8, hashMap.Capacity, "HashMap grew larger than expected"); 273 // Make sure reading the inserted values work 274 for (int i = 0; i < 8; ++i) 275 { 276 Assert.IsTrue(hashMap.TryGetValue(i, out iSquared), "Failed get value from hash table"); 277 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table"); 278 } 279 for (int rm = 0; rm < 8; ++rm) 280 { 281 Assert.IsTrue(hashMap.Remove(rm)); 282 Assert.IsFalse(hashMap.TryGetValue(rm, out iSquared), "Failed to remove value from hash table"); 283 for (int i = rm + 1; i < 8; ++i) 284 { 285 Assert.IsTrue(hashMap.TryGetValue(i, out iSquared), "Failed get value from hash table"); 286 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table"); 287 } 288 } 289 // Make sure entries were freed 290 for (int i = 0; i < 8; ++i) 291 Assert.IsTrue(hashMap.TryAdd(i, i * i), "Failed to add value"); 292 Assert.AreEqual(8, hashMap.Capacity, "HashMap grew larger than expected"); 293 hashMap.Dispose(); 294 } 295 296 [Test] 297 public void NativeParallelHashMap_RemoveOnEmptyMap_DoesNotThrow() 298 { 299 var hashMap = new NativeParallelHashMap<int, int>(0, Allocator.Temp); 300 Assert.DoesNotThrow(() => hashMap.Remove(0)); 301 Assert.DoesNotThrow(() => hashMap.Remove(-425196)); 302 hashMap.Dispose(); 303 } 304 305 [Test] 306 public void NativeParallelHashMap_TryAddScalability() 307 { 308 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Persistent); 309 for (int i = 0; i != 1000 * 100; i++) 310 { 311 hashMap.TryAdd(i, i); 312 } 313 314 int value; 315 Assert.IsFalse(hashMap.TryGetValue(-1, out value)); 316 Assert.IsFalse(hashMap.TryGetValue(1000 * 1000, out value)); 317 318 for (int i = 0; i != 1000 * 100; i++) 319 { 320 Assert.IsTrue(hashMap.TryGetValue(i, out value)); 321 Assert.AreEqual(i, value); 322 } 323 324 hashMap.Dispose(); 325 } 326 327 [Test] 328 public void NativeParallelHashMap_GetKeysEmpty() 329 { 330 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Temp); 331 var keys = hashMap.GetKeyArray(Allocator.Temp); 332 hashMap.Dispose(); 333 334 Assert.AreEqual(0, keys.Length); 335 keys.Dispose(); 336 } 337 338 [Test] 339 public void NativeParallelHashMap_GetKeys() 340 { 341 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Temp); 342 for (int i = 0; i < 30; ++i) 343 { 344 hashMap.TryAdd(i, 2 * i); 345 } 346 var keys = hashMap.GetKeyArray(Allocator.Temp); 347 hashMap.Dispose(); 348 349 Assert.AreEqual(30, keys.Length); 350 keys.Sort(); 351 for (int i = 0; i < 30; ++i) 352 { 353 Assert.AreEqual(i, keys[i]); 354 } 355 keys.Dispose(); 356 } 357 358 [Test] 359 public void NativeParallelHashMap_GetValues() 360 { 361 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Temp); 362 for (int i = 0; i < 30; ++i) 363 { 364 hashMap.TryAdd(i, 2 * i); 365 } 366 var values = hashMap.GetValueArray(Allocator.Temp); 367 hashMap.Dispose(); 368 369 Assert.AreEqual(30, values.Length); 370 values.Sort(); 371 for (int i = 0; i < 30; ++i) 372 { 373 Assert.AreEqual(2 * i, values[i]); 374 } 375 values.Dispose(); 376 } 377 378 [Test] 379 public void NativeParallelHashMap_GetKeysAndValues() 380 { 381 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Temp); 382 for (int i = 0; i < 30; ++i) 383 { 384 hashMap.TryAdd(i, 2 * i); 385 } 386 var keysValues = hashMap.GetKeyValueArrays(Allocator.Temp); 387 hashMap.Dispose(); 388 389 Assert.AreEqual(30, keysValues.Keys.Length); 390 Assert.AreEqual(30, keysValues.Values.Length); 391 392 // ensure keys and matching values are aligned 393 for (int i = 0; i < 30; ++i) 394 { 395 Assert.AreEqual(2 * keysValues.Keys[i], keysValues.Values[i]); 396 } 397 398 keysValues.Keys.Sort(); 399 for (int i = 0; i < 30; ++i) 400 { 401 Assert.AreEqual(i, keysValues.Keys[i]); 402 } 403 404 keysValues.Values.Sort(); 405 for (int i = 0; i < 30; ++i) 406 { 407 Assert.AreEqual(2 * i, keysValues.Values[i]); 408 } 409 410 keysValues.Dispose(); 411 } 412 413 public struct TestEntityGuid : IEquatable<TestEntityGuid>, IComparable<TestEntityGuid> 414 { 415 public ulong a; 416 public ulong b; 417 418 public bool Equals(TestEntityGuid other) 419 { 420 return a == other.a && b == other.b; 421 } 422 423 public override int GetHashCode() 424 { 425 unchecked 426 { 427 return (a.GetHashCode() * 397) ^ b.GetHashCode(); 428 } 429 } 430 431 public int CompareTo(TestEntityGuid other) 432 { 433 var aComparison = a.CompareTo(other.a); 434 if (aComparison != 0) return aComparison; 435 return b.CompareTo(other.b); 436 } 437 } 438 439 [Test] 440 public void NativeParallelHashMap_GetKeysGuid() 441 { 442 var hashMap = new NativeParallelHashMap<TestEntityGuid, int>(1, Allocator.Temp); 443 for (int i = 0; i < 30; ++i) 444 { 445 var didAdd = hashMap.TryAdd(new TestEntityGuid() { a = (ulong)i * 5, b = 3 * (ulong)i }, 2 * i); 446 Assert.IsTrue(didAdd); 447 } 448 449 // Validate Hashtable has all the expected values 450 ExpectedCount(ref hashMap, 30); 451 for (int i = 0; i < 30; ++i) 452 { 453 int output; 454 var exists = hashMap.TryGetValue(new TestEntityGuid() { a = (ulong)i * 5, b = 3 * (ulong)i }, out output); 455 Assert.IsTrue(exists); 456 Assert.AreEqual(2 * i, output); 457 } 458 459 // Validate keys array 460 var keys = hashMap.GetKeyArray(Allocator.Temp); 461 Assert.AreEqual(30, keys.Length); 462 463 keys.Sort(); 464 for (int i = 0; i < 30; ++i) 465 { 466 Assert.AreEqual(new TestEntityGuid() { a = (ulong)i * 5, b = 3 * (ulong)i }, keys[i]); 467 } 468 469 hashMap.Dispose(); 470 keys.Dispose(); 471 } 472 473 [Test] 474 public void NativeParallelHashMap_IndexerWorks() 475 { 476 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Temp); 477 hashMap[5] = 7; 478 Assert.AreEqual(7, hashMap[5]); 479 480 hashMap[5] = 9; 481 Assert.AreEqual(9, hashMap[5]); 482 483 hashMap.Dispose(); 484 } 485 486 [Test] 487 public void NativeParallelHashMap_ContainsKeyHashMap() 488 { 489 var hashMap = new NativeParallelHashMap<int, int>(1, Allocator.Temp); 490 hashMap[5] = 7; 491 492 Assert.IsTrue(hashMap.ContainsKey(5)); 493 Assert.IsFalse(hashMap.ContainsKey(6)); 494 495 hashMap.Dispose(); 496 } 497 498 [Test] 499 public void NativeParallelHashMap_NativeKeyValueArrays_DisposeJob() 500 { 501 var container = new NativeParallelHashMap<int, int>(1, Allocator.Persistent); 502 Assert.True(container.IsCreated); 503 Assert.DoesNotThrow(() => { container[0] = 0; }); 504 Assert.DoesNotThrow(() => { container[1] = 1; }); 505 Assert.DoesNotThrow(() => { container[2] = 2; }); 506 Assert.DoesNotThrow(() => { container[3] = 3; }); 507 508 var kv = container.GetKeyValueArrays(Allocator.Persistent); 509 510 var disposeJob = container.Dispose(default); 511 Assert.False(container.IsCreated); 512#if ENABLE_UNITY_COLLECTIONS_CHECKS 513 Assert.Throws<ObjectDisposedException>( 514 () => { container[0] = 2; }); 515#endif 516 517 kv.Dispose(disposeJob); 518 519 disposeJob.Complete(); 520 } 521 522 [Test] 523 [TestRequiresCollectionChecks] 524 public void NativeParallelHashMap_UseAfterFree_UsesCustomOwnerTypeName() 525 { 526 var container = new NativeParallelHashMap<int, int>(10, CommonRwdAllocator.Handle); 527 container[0] = 123; 528 container.Dispose(); 529 NUnit.Framework.Assert.That(() => container[0], 530 Throws.Exception.TypeOf<ObjectDisposedException>() 531 .With.Message.Contains($"The {container.GetType()} has been deallocated")); 532 } 533 534 [BurstCompile(CompileSynchronously = true)] 535 struct NativeParallelHashMap_CreateAndUseAfterFreeBurst : IJob 536 { 537 public void Execute() 538 { 539 var container = new NativeParallelHashMap<int, int>(10, Allocator.Temp); 540 container[0] = 17; 541 container.Dispose(); 542 container[1] = 42; 543 } 544 } 545 546 [Test] 547 [TestRequiresCollectionChecks] 548 public void NativeParallelHashMap_CreateAndUseAfterFreeInBurstJob_UsesCustomOwnerTypeName() 549 { 550 // Make sure this isn't the first container of this type ever created, so that valid static safety data exists 551 var container = new NativeParallelHashMap<int, int>(10, CommonRwdAllocator.Handle); 552 container.Dispose(); 553 554 var job = new NativeParallelHashMap_CreateAndUseAfterFreeBurst 555 { 556 }; 557 558 // Two things: 559 // 1. This exception is logged, not thrown; thus, we use LogAssert to detect it. 560 // 2. Calling write operation after container.Dispose() emits an unintuitive error message. For now, all this test cares about is whether it contains the 561 // expected type name. 562 job.Run(); 563 LogAssert.Expect(LogType.Exception, 564 new Regex($"InvalidOperationException: The {Regex.Escape(container.GetType().ToString())} has been declared as \\[ReadOnly\\] in the job, but you are writing to it")); 565 } 566 567 [Test] 568 public void NativeParallelHashMap_ForEach_FixedStringInHashMap() 569 { 570 using (var stringList = new NativeList<FixedString32Bytes>(10, Allocator.Persistent) { "Hello", ",", "World", "!" }) 571 { 572 var seen = new NativeArray<int>(stringList.Length, Allocator.Temp); 573 var container = new NativeParallelHashMap<FixedString128Bytes, float>(50, Allocator.Temp); 574 foreach (var str in stringList) 575 { 576 container.Add(str, 0); 577 } 578 579 foreach (var pair in container) 580 { 581 int index = stringList.IndexOf(pair.Key); 582 Assert.AreEqual(stringList[index], pair.Key.ToString()); 583 seen[index] = seen[index] + 1; 584 } 585 586 for (int i = 0; i < stringList.Length; i++) 587 { 588 Assert.AreEqual(1, seen[i], $"Incorrect value count {stringList[i]}"); 589 } 590 } 591 } 592 593 [Test] 594 public void NativeParallelHashMap_EnumeratorDoesNotReturnRemovedElementsTest() 595 { 596 NativeParallelHashMap<int, int> container = new NativeParallelHashMap<int, int>(5, Allocator.Temp); 597 for (int i = 0; i < 5; i++) 598 { 599 container.Add(i, i); 600 } 601 602 int elementToRemove = 2; 603 container.Remove(elementToRemove); 604 605 using (var enumerator = container.GetEnumerator()) 606 { 607 while (enumerator.MoveNext()) 608 { 609 Assert.AreNotEqual(elementToRemove, enumerator.Current.Key); 610 } 611 } 612 613 container.Dispose(); 614 } 615 616 [Test] 617 public void NativeParallelHashMap_EnumeratorInfiniteIterationTest() 618 { 619 NativeParallelHashMap<int, int> container = new NativeParallelHashMap<int, int>(5, Allocator.Temp); 620 for (int i = 0; i < 5; i++) 621 { 622 container.Add(i, i); 623 } 624 625 for (int i = 0; i < 2; i++) 626 { 627 container.Remove(i); 628 } 629 630 var expected = container.Count(); 631 int count = 0; 632 using (var enumerator = container.GetEnumerator()) 633 { 634 while (enumerator.MoveNext()) 635 { 636 if (count++ > expected) 637 { 638 break; 639 } 640 } 641 } 642 643 Assert.AreEqual(expected, count); 644 container.Dispose(); 645 } 646 647 [Test] 648 public void NativeParallelHashMap_ForEach([Values(10, 1000)]int n) 649 { 650 var seen = new NativeArray<int>(n, Allocator.Temp); 651 using (var container = new NativeParallelHashMap<int, int>(32, CommonRwdAllocator.Handle)) 652 { 653 for (int i = 0; i < n; i++) 654 { 655 container.Add(i, i * 37); 656 } 657 658 var count = 0; 659 foreach (var kv in container) 660 { 661 int value; 662 Assert.True(container.TryGetValue(kv.Key, out value)); 663 Assert.AreEqual(value, kv.Value); 664 Assert.AreEqual(kv.Key * 37, kv.Value); 665 666 seen[kv.Key] = seen[kv.Key] + 1; 667 ++count; 668 } 669 670 Assert.AreEqual(container.Count(), count); 671 for (int i = 0; i < n; i++) 672 { 673 Assert.AreEqual(1, seen[i], $"Incorrect key count {i}"); 674 } 675 } 676 } 677 678 struct NativeParallelHashMap_ForEach_Job : IJob 679 { 680 public NativeParallelHashMap<int, int>.ReadOnly Input; 681 682 [ReadOnly] 683 public int Num; 684 685 public void Execute() 686 { 687 var seen = new NativeArray<int>(Num, Allocator.Temp); 688 689 var count = 0; 690 foreach (var kv in Input) 691 { 692 int value; 693 Assert.True(Input.TryGetValue(kv.Key, out value)); 694 Assert.AreEqual(value, kv.Value); 695 Assert.AreEqual(kv.Key * 37, kv.Value); 696 697 seen[kv.Key] = seen[kv.Key] + 1; 698 ++count; 699 } 700 701 Assert.AreEqual(Input.Count(), count); 702 for (int i = 0; i < Num; i++) 703 { 704 Assert.AreEqual(1, seen[i], $"Incorrect key count {i}"); 705 } 706 707 seen.Dispose(); 708 } 709 } 710 711 [Test] 712 public void NativeParallelHashMap_ForEach_From_Job([Values(10, 1000)] int n) 713 { 714 var seen = new NativeArray<int>(n, Allocator.Temp); 715 using (var container = new NativeParallelHashMap<int, int>(32, CommonRwdAllocator.Handle)) 716 { 717 for (int i = 0; i < n; i++) 718 { 719 container.Add(i, i * 37); 720 } 721 722 new NativeParallelHashMap_ForEach_Job 723 { 724 Input = container.AsReadOnly(), 725 Num = n, 726 727 }.Run(); 728 } 729 } 730 731 [Test] 732 [TestRequiresCollectionChecks] 733 public void NativeParallelHashMap_ForEach_Throws_When_Modified() 734 { 735 using (var container = new NativeParallelHashMap<int, int>(32, CommonRwdAllocator.Handle)) 736 { 737 container.Add(0, 012); 738 container.Add(1, 123); 739 container.Add(2, 234); 740 container.Add(3, 345); 741 container.Add(4, 456); 742 container.Add(5, 567); 743 container.Add(6, 678); 744 container.Add(7, 789); 745 container.Add(8, 890); 746 container.Add(9, 901); 747 748 Assert.Throws<ObjectDisposedException>(() => 749 { 750 foreach (var kv in container) 751 { 752 container.Add(10, 10); 753 } 754 }); 755 756 Assert.Throws<ObjectDisposedException>(() => 757 { 758 foreach (var kv in container) 759 { 760 container.Remove(1); 761 } 762 }); 763 } 764 } 765 766 struct NativeParallelHashMap_ForEachIterator : IJob 767 { 768 [ReadOnly] 769 public NativeParallelHashMap<int, int>.Enumerator Iter; 770 771 public void Execute() 772 { 773 while (Iter.MoveNext()) 774 { 775 } 776 } 777 } 778 779 [Test] 780 [TestRequiresCollectionChecks] 781 public void NativeParallelHashMap_ForEach_Throws_Job_Iterator() 782 { 783 using (var container = new NativeParallelHashMap<int, int>(32, CommonRwdAllocator.Handle)) 784 { 785 var jobHandle = new NativeParallelHashMap_ForEachIterator 786 { 787 Iter = container.GetEnumerator() 788 789 }.Schedule(); 790 791 Assert.Throws<InvalidOperationException>(() => { container.Add(1, 1); }); 792 793 jobHandle.Complete(); 794 } 795 } 796 797 struct ParallelWriteToHashMapJob : IJobParallelFor 798 { 799 [WriteOnly] 800 public NativeParallelHashMap<int, int>.ParallelWriter Writer; 801 802 public void Execute(int index) 803 { 804 Writer.TryAdd(index, 0); 805 } 806 } 807 808 [Test] 809 [TestRequiresCollectionChecks] 810 public void NativeParallelHashMap_ForEach_Throws() 811 { 812 using (var container = new NativeParallelHashMap<int, int>(32, CommonRwdAllocator.Handle)) 813 { 814 var iter = container.GetEnumerator(); 815 816 var jobHandle = new ParallelWriteToHashMapJob 817 { 818 Writer = container.AsParallelWriter() 819 820 }.Schedule(1, 2); 821 822 Assert.Throws<ObjectDisposedException>(() => 823 { 824 while (iter.MoveNext()) 825 { 826 } 827 }); 828 829 jobHandle.Complete(); 830 } 831 } 832 833 [Test] 834 public unsafe void NativeParallelHashMap_GetUnsafeBucketData() 835 { 836 using (var container = new NativeParallelHashMap<int, int>(32, CommonRwdAllocator.Handle)) 837 { 838 container.Add(0, 012); 839 container.Add(1, 123); 840 container.Add(2, 234); 841 container.Add(3, 345); 842 container.Add(4, 456); 843 container.Add(5, 567); 844 container.Add(6, 678); 845 container.Add(7, 789); 846 container.Add(8, 890); 847 container.Add(9, 901); 848 849 var bucketData = container.GetUnsafeBucketData(); 850 851 var buckets = (int*)bucketData.buckets; 852 var nextPtrs = (int*)bucketData.next; 853 var keys = bucketData.keys; 854 var values = bucketData.values; 855 856 var other = new NativeParallelHashMap<int, int>(32, CommonRwdAllocator.Handle); 857 858 for (int i = 0, count = container.Count(); i < count; i++) 859 { 860 int entryIndex = buckets[i]; 861 862 while (entryIndex != -1) 863 { 864 var bdKey = UnsafeUtility.ReadArrayElement<int>(keys, entryIndex); 865 var bdValue = UnsafeUtility.ArrayElementAsRef<int>(values, entryIndex); 866 867 other.Add(bdKey, bdValue); 868 869 entryIndex = nextPtrs[entryIndex]; 870 } 871 } 872 873 Assert.AreEqual(container.Count(), other.Count()); 874 875 var kvArray = container.GetKeyValueArrays(CommonRwdAllocator.Handle); 876 877 for (int i = 0, count = kvArray.Length; i < count; i++) 878 { 879 int value; 880 Assert.True(other.TryGetValue(kvArray.Keys[i], out value)); 881 Assert.AreEqual(value, kvArray.Values[i]); 882 } 883 884 kvArray.Dispose(); 885 other.Dispose(); 886 } 887 } 888 889 [Test] 890 public void NativeParallelHashMap_CustomAllocatorTest() 891 { 892 AllocatorManager.Initialize(); 893 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent); 894 ref var allocator = ref allocatorHelper.Allocator; 895 allocator.Initialize(); 896 897 using (var container = new NativeParallelHashMap<int, int>(1, allocator.Handle)) 898 { 899 } 900 901 Assert.IsTrue(allocator.WasUsed); 902 allocator.Dispose(); 903 allocatorHelper.Dispose(); 904 AllocatorManager.Shutdown(); 905 } 906 907 [BurstCompile] 908 struct BurstedCustomAllocatorJob : IJob 909 { 910 [NativeDisableUnsafePtrRestriction] 911 public unsafe CustomAllocatorTests.CountingAllocator* Allocator; 912 913 public void Execute() 914 { 915 unsafe 916 { 917 using (var container = new NativeParallelHashMap<int, int>(1, Allocator->Handle)) 918 { 919 } 920 } 921 } 922 } 923 924 [Test] 925 public unsafe void NativeParallelHashMap_BurstedCustomAllocatorTest() 926 { 927 AllocatorManager.Initialize(); 928 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent); 929 ref var allocator = ref allocatorHelper.Allocator; 930 allocator.Initialize(); 931 932 var allocatorPtr = (CustomAllocatorTests.CountingAllocator*)UnsafeUtility.AddressOf<CustomAllocatorTests.CountingAllocator>(ref allocator); 933 unsafe 934 { 935 var handle = new BurstedCustomAllocatorJob {Allocator = allocatorPtr}.Schedule(); 936 handle.Complete(); 937 } 938 939 Assert.IsTrue(allocator.WasUsed); 940 allocator.Dispose(); 941 allocatorHelper.Dispose(); 942 AllocatorManager.Shutdown(); 943 } 944 945 public struct NestedHashMap 946 { 947 public NativeParallelHashMap<int, int> map; 948 } 949 950 [Test] 951 public void NativeParallelHashMap_Nested() 952 { 953 var mapInner = new NativeParallelHashMap<int, int>(16, CommonRwdAllocator.Handle); 954 NestedHashMap mapStruct = new NestedHashMap { map = mapInner }; 955 956 var mapNestedStruct = new NativeParallelHashMap<int, NestedHashMap>(16, CommonRwdAllocator.Handle); 957 var mapNested = new NativeParallelHashMap<int, NativeParallelHashMap<int, int>>(16, CommonRwdAllocator.Handle); 958 959 mapNested[14] = mapInner; 960 mapNestedStruct[17] = mapStruct; 961 962 mapNested.Dispose(); 963 mapNestedStruct.Dispose(); 964 mapInner.Dispose(); 965 } 966 967 [Test] 968 public void NativeParallelHashMap_IndexerAdd_ResizesContainer() 969 { 970 var container = new NativeParallelHashMap<int, int>(8, Allocator.Persistent); 971 for (int i = 0; i < 1024; i++) 972 { 973 container[i] = i; 974 } 975 Assert.AreEqual(1024, container.Count()); 976 container.Dispose(); 977 } 978}