A game about forced loneliness, made by TACStudios
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}