A game about forced loneliness, made by TACStudios
1using System;
2using NUnit.Framework;
3using Unity.Burst;
4using Unity.Collections;
5using Unity.Collections.LowLevel.Unsafe;
6using Unity.Collections.NotBurstCompatible;
7using Unity.Jobs;
8using Unity.Collections.Tests;
9
10internal class NativeParallelHashSetTests: CollectionsTestFixture
11{
12 static void ExpectedCount<T>(ref NativeParallelHashSet<T> container, int expected)
13 where T : unmanaged, IEquatable<T>
14 {
15 Assert.AreEqual(expected == 0, container.IsEmpty);
16 Assert.AreEqual(expected, container.Count());
17 }
18
19 [Test]
20 public void NativeParallelHashSet_IsEmpty()
21 {
22 var container = new NativeParallelHashSet<int>(0, Allocator.Persistent);
23 Assert.IsTrue(container.IsEmpty);
24
25 Assert.IsTrue(container.Add(0));
26 Assert.IsFalse(container.IsEmpty);
27 Assert.AreEqual(1, container.Capacity);
28 ExpectedCount(ref container, 1);
29
30 container.Remove(0);
31 Assert.IsTrue(container.IsEmpty);
32
33 Assert.IsTrue(container.Add(0));
34 container.Clear();
35 Assert.IsTrue(container.IsEmpty);
36
37 container.Dispose();
38 }
39
40 [Test]
41 public void UnsafeParallelHashSet_Capacity()
42 {
43 var container = new NativeParallelHashSet<int>(0, Allocator.Persistent);
44 Assert.IsTrue(container.IsEmpty);
45 Assert.AreEqual(0, container.Capacity);
46
47 container.Capacity = 10;
48 Assert.AreEqual(10, container.Capacity);
49
50 container.Dispose();
51 }
52
53 [Test]
54 [TestRequiresCollectionChecks]
55 public void NativeParallelHashSet_Full_Throws()
56 {
57 var container = new NativeParallelHashSet<int>(16, Allocator.Temp);
58 ExpectedCount(ref container, 0);
59
60 for (int i = 0, capacity = container.Capacity; i < capacity; ++i)
61 {
62 Assert.DoesNotThrow(() => { container.Add(i); });
63 }
64 ExpectedCount(ref container, container.Capacity);
65
66 // Make sure overallocating throws and exception if using the Concurrent version - normal hash map would grow
67 var writer = container.AsParallelWriter();
68 Assert.Throws<System.InvalidOperationException>(() => { writer.Add(100); });
69 ExpectedCount(ref container, container.Capacity);
70
71 container.Clear();
72 ExpectedCount(ref container, 0);
73
74 container.Dispose();
75 }
76
77 [Test]
78 public void NativeParallelHashSet_RemoveOnEmptyMap_DoesNotThrow()
79 {
80 var container = new NativeParallelHashSet<int>(0, Allocator.Temp);
81 Assert.DoesNotThrow(() => container.Remove(0));
82 Assert.DoesNotThrow(() => container.Remove(-425196));
83 container.Dispose();
84 }
85
86 [Test]
87 public void NativeParallelHashSet_Collisions()
88 {
89 var container = new NativeParallelHashSet<int>(16, Allocator.Temp);
90
91 Assert.IsFalse(container.Contains(0), "Contains on empty hash map did not fail");
92 ExpectedCount(ref container, 0);
93
94 // Make sure inserting values work
95 for (int i = 0; i < 8; ++i)
96 {
97 Assert.IsTrue(container.Add(i), "Failed to add value");
98 }
99 ExpectedCount(ref container, 8);
100
101 // The bucket size is capacity * 2, adding that number should result in hash collisions
102 for (int i = 0; i < 8; ++i)
103 {
104 Assert.IsTrue(container.Add(i + 32), "Failed to add value with potential hash collision");
105 }
106
107 // Make sure reading the inserted values work
108 for (int i = 0; i < 8; ++i)
109 {
110 Assert.IsTrue(container.Contains(i), "Failed get value from hash set");
111 }
112
113 for (int i = 0; i < 8; ++i)
114 {
115 Assert.IsTrue(container.Contains(i + 32), "Failed get value from hash set");
116 }
117
118 container.Dispose();
119 }
120
121 [Test]
122 public void NativeParallelHashSet_SameElement()
123 {
124 using (var container = new NativeParallelHashSet<int>(0, Allocator.Persistent))
125 {
126 Assert.IsTrue(container.Add(0));
127 Assert.IsFalse(container.Add(0));
128 }
129 }
130
131 [Test]
132 public void NativeParallelHashSet_ParallelWriter_CanBeUsedInJob()
133 {
134 const int count = 32;
135 using (var hashSet = new NativeParallelHashSet<int>(count, CommonRwdAllocator.Handle))
136 {
137 new ParallelWriteToHashSetJob
138 {
139 Writer = hashSet.AsParallelWriter()
140 }.Schedule(count, 2).Complete();
141
142 var result = hashSet.ToNativeArray(Allocator.Temp);
143 result.Sort();
144 for (int i = 0; i < count; i++)
145 Assert.AreEqual(i, result[i]);
146 }
147 }
148
149 struct ParallelWriteToHashSetJob : IJobParallelFor
150 {
151 [WriteOnly]
152 public NativeParallelHashSet<int>.ParallelWriter Writer;
153
154 public void Execute(int index)
155 {
156 Writer.Add(index);
157 }
158 }
159
160 [Test]
161 public void NativeParallelHashSet_CanBeReadFromJob()
162 {
163 using (var hashSet = new NativeParallelHashSet<int>(1, CommonRwdAllocator.Handle))
164 using (var result = new NativeReference<int>(CommonRwdAllocator.Handle))
165 {
166 hashSet.Add(42);
167 new ReadHashSetJob
168 {
169 Input = hashSet.AsReadOnly(),
170 Output = result,
171 }.Run();
172 Assert.AreEqual(42, result.Value);
173 }
174 }
175
176 struct TempHashSet : IJob
177 {
178 public void Execute()
179 {
180 using (var stringList = new NativeList<FixedString32Bytes>(10, Allocator.Persistent) { "Hello", ",", "World", "!" })
181 {
182 var container = new NativeParallelHashSet<FixedString128Bytes>(50, Allocator.Temp);
183 var seen = new NativeArray<int>(stringList.Length, Allocator.Temp);
184 foreach (var str in stringList)
185 {
186 container.Add(str);
187 }
188
189 foreach (var value in container)
190 {
191 int index = stringList.IndexOf(value);
192 Assert.AreEqual(stringList[index], value.ToString());
193 seen[index] = seen[index] + 1;
194 }
195
196 for (int i = 0; i < stringList.Length; i++)
197 {
198 Assert.AreEqual(1, seen[i], $"Incorrect value count {stringList[i]}");
199 }
200 }
201 }
202 }
203
204 [Test]
205 public void NativeParallelHashSet_TempHashSetInJob()
206 {
207 new TempHashSet { }.Schedule().Complete();
208 }
209
210 struct ReadHashSetJob : IJob
211 {
212 [ReadOnly]
213 public NativeParallelHashSet<int>.ReadOnly Input;
214
215 public NativeReference<int> Output;
216 public void Execute()
217 {
218 Output.Value = Input.ToNativeArray(Allocator.Temp)[0];
219
220 foreach (var value in Input)
221 {
222 Assert.AreEqual(42, value);
223 }
224 }
225 }
226
227 [Test]
228 public void NativeParallelHashSet_ForEach_FixedStringInHashMap()
229 {
230 using (var stringList = new NativeList<FixedString32Bytes>(10, Allocator.Persistent) { "Hello", ",", "World", "!" })
231 {
232 var container = new NativeParallelHashSet<FixedString128Bytes>(50, Allocator.Temp);
233 var seen = new NativeArray<int>(stringList.Length, Allocator.Temp);
234 foreach (var str in stringList)
235 {
236 container.Add(str);
237 }
238
239 foreach (var value in container)
240 {
241 int index = stringList.IndexOf(value);
242 Assert.AreEqual(stringList[index], value.ToString());
243 seen[index] = seen[index] + 1;
244 }
245
246 for (int i = 0; i < stringList.Length; i++)
247 {
248 Assert.AreEqual(1, seen[i], $"Incorrect value count {stringList[i]}");
249 }
250 }
251 }
252
253 [Test]
254 public void NativeParallelHashSet_ForEach([Values(10, 1000)]int n)
255 {
256 var seen = new NativeArray<int>(n, Allocator.Temp);
257 using (var container = new NativeParallelHashSet<int>(32, CommonRwdAllocator.Handle))
258 {
259 for (int i = 0; i < n; i++)
260 {
261 container.Add(i);
262 }
263
264 var count = 0;
265 foreach (var item in container)
266 {
267 Assert.True(container.Contains(item));
268 seen[item] = seen[item] + 1;
269 ++count;
270 }
271
272 Assert.AreEqual(container.Count(), count);
273 for (int i = 0; i < n; i++)
274 {
275 Assert.AreEqual(1, seen[i], $"Incorrect item count {i}");
276 }
277 }
278 }
279
280 struct NativeParallelHashSet_ForEach_Job : IJob
281 {
282 [ReadOnly]
283 public NativeParallelHashSet<int>.ReadOnly Input;
284
285 [ReadOnly]
286 public int Num;
287
288 public void Execute()
289 {
290 var seen = new NativeArray<int>(Num, Allocator.Temp);
291
292 var count = 0;
293 foreach (var item in Input)
294 {
295 Assert.True(Input.Contains(item));
296 seen[item] = seen[item] + 1;
297 ++count;
298 }
299
300 Assert.AreEqual(Input.Count(), count);
301 for (int i = 0; i < Num; i++)
302 {
303 Assert.AreEqual(1, seen[i], $"Incorrect item count {i}");
304 }
305
306 seen.Dispose();
307 }
308 }
309
310 [Test]
311 public void NativeParallelHashSet_ForEach_From_Job([Values(10, 1000)] int n)
312 {
313 using (var container = new NativeParallelHashSet<int>(32, CommonRwdAllocator.Handle))
314 {
315 for (int i = 0; i < n; i++)
316 {
317 container.Add(i);
318 }
319
320 new NativeParallelHashSet_ForEach_Job
321 {
322 Input = container.AsReadOnly(),
323 Num = n,
324
325 }.Run();
326 }
327 }
328
329 [Test]
330 [TestRequiresCollectionChecks]
331 public void NativeParallelHashSet_ForEach_Throws_When_Modified()
332 {
333 using (var container = new NativeParallelHashSet<int>(32, CommonRwdAllocator.Handle))
334 {
335 container.Add(0);
336 container.Add(1);
337 container.Add(2);
338 container.Add(3);
339 container.Add(4);
340 container.Add(5);
341 container.Add(6);
342 container.Add(7);
343 container.Add(8);
344 container.Add(9);
345
346 Assert.Throws<ObjectDisposedException>(() =>
347 {
348 foreach (var item in container)
349 {
350 container.Add(10);
351 }
352 });
353
354 Assert.Throws<ObjectDisposedException>(() =>
355 {
356 foreach (var item in container)
357 {
358 container.Remove(1);
359 }
360 });
361 }
362 }
363
364 [Test]
365 [TestRequiresCollectionChecks]
366 public void NativeParallelHashSet_ForEach_Throws()
367 {
368 using (var container = new NativeParallelHashSet<int>(32, CommonRwdAllocator.Handle))
369 {
370 var iter = container.GetEnumerator();
371
372 var jobHandle = new ParallelWriteToHashSetJob
373 {
374 Writer = container.AsParallelWriter()
375
376 }.Schedule(1, 2);
377
378 Assert.Throws<ObjectDisposedException>(() =>
379 {
380 while (iter.MoveNext())
381 {
382 }
383 });
384
385 jobHandle.Complete();
386 }
387 }
388
389 struct ForEachIterator : IJob
390 {
391 [ReadOnly]
392 public NativeParallelHashSet<int>.Enumerator Iter;
393
394 public void Execute()
395 {
396 while (Iter.MoveNext())
397 {
398 }
399 }
400 }
401
402 [Test]
403 [TestRequiresCollectionChecks]
404 public void NativeParallelHashSet_ForEach_Throws_Job_Iterator()
405 {
406 using (var container = new NativeParallelHashSet<int>(32, CommonRwdAllocator.Handle))
407 {
408 var jobHandle = new ForEachIterator
409 {
410 Iter = container.GetEnumerator()
411
412 }.Schedule();
413
414 Assert.Throws<InvalidOperationException>(() => { container.Add(1); });
415
416 jobHandle.Complete();
417 }
418 }
419
420 [Test]
421 public void NativeParallelHashSet_EIU_ExceptWith_Empty()
422 {
423 var setA = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { };
424 var setB = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { };
425 setA.ExceptWith(setB);
426
427 ExpectedCount(ref setA, 0);
428
429 setA.Dispose();
430 setB.Dispose();
431 }
432
433 [Test]
434 public void NativeParallelHashSet_EIU_ExceptWith_AxB()
435 {
436 var setA = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 0, 1, 2, 3, 4, 5 };
437 var setB = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 3, 4, 5, 6, 7, 8 };
438 setA.ExceptWith(setB);
439
440 ExpectedCount(ref setA, 3);
441 Assert.True(setA.Contains(0));
442 Assert.True(setA.Contains(1));
443 Assert.True(setA.Contains(2));
444
445 setA.Dispose();
446 setB.Dispose();
447 }
448
449 [Test]
450 public void NativeParallelHashSet_EIU_ExceptWith_BxA()
451 {
452 var setA = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 0, 1, 2, 3, 4, 5 };
453 var setB = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 3, 4, 5, 6, 7, 8 };
454 setB.ExceptWith(setA);
455
456 ExpectedCount(ref setB, 3);
457 Assert.True(setB.Contains(6));
458 Assert.True(setB.Contains(7));
459 Assert.True(setB.Contains(8));
460
461 setA.Dispose();
462 setB.Dispose();
463 }
464
465 [Test]
466 public void NativeParallelHashSet_EIU_IntersectWith_Empty()
467 {
468 var setA = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { };
469 var setB = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { };
470 setA.IntersectWith(setB);
471
472 ExpectedCount(ref setA, 0);
473
474 setA.Dispose();
475 setB.Dispose();
476 }
477
478 [Test]
479 public void NativeParallelHashSet_EIU_IntersectWith()
480 {
481 var setA = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 0, 1, 2, 3, 4, 5 };
482 var setB = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 3, 4, 5, 6, 7, 8 };
483 setA.IntersectWith(setB);
484
485 ExpectedCount(ref setA, 3);
486 Assert.True(setA.Contains(3));
487 Assert.True(setA.Contains(4));
488 Assert.True(setA.Contains(5));
489
490 setA.Dispose();
491 setB.Dispose();
492 }
493
494 [Test]
495 public void NativeParallelHashSet_EIU_UnionWith_Empty()
496 {
497 var setA = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { };
498 var setB = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { };
499 setA.UnionWith(setB);
500
501 ExpectedCount(ref setA, 0);
502
503 setA.Dispose();
504 setB.Dispose();
505 }
506
507 [Test]
508 public void NativeParallelHashSet_EIU_UnionWith()
509 {
510 var setA = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 0, 1, 2, 3, 4, 5 };
511 var setB = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 3, 4, 5, 6, 7, 8 };
512 setA.UnionWith(setB);
513
514 ExpectedCount(ref setA, 9);
515 Assert.True(setA.Contains(0));
516 Assert.True(setA.Contains(1));
517 Assert.True(setA.Contains(2));
518 Assert.True(setA.Contains(3));
519 Assert.True(setA.Contains(4));
520 Assert.True(setA.Contains(5));
521 Assert.True(setA.Contains(6));
522 Assert.True(setA.Contains(7));
523 Assert.True(setA.Contains(8));
524
525 setA.Dispose();
526 setB.Dispose();
527 }
528
529 [Test]
530 public void NativeParallelHashSet_ToArray()
531 {
532 using (var set = new NativeParallelHashSet<int>(8, CommonRwdAllocator.Handle) { 0, 1, 2, 3, 4, 5 })
533 {
534 var array = set.ToArray();
535 Array.Sort(array);
536 for (int i = 0, num = set.Count(); i < num; i++)
537 {
538 Assert.AreEqual(array[i], i);
539 }
540 }
541 }
542
543 [Test]
544 public void NativeParallelHashSet_CustomAllocatorTest()
545 {
546 AllocatorManager.Initialize();
547 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
548 ref var allocator = ref allocatorHelper.Allocator;
549 allocator.Initialize();
550
551 using (var container = new NativeParallelHashSet<int>(1, allocator.Handle))
552 {
553 }
554
555 FastAssert.IsTrue(allocator.WasUsed);
556 allocator.Dispose();
557 allocatorHelper.Dispose();
558 AllocatorManager.Shutdown();
559 }
560
561 [BurstCompile]
562 struct BurstedCustomAllocatorJob : IJob
563 {
564 [NativeDisableUnsafePtrRestriction]
565 public unsafe CustomAllocatorTests.CountingAllocator* Allocator;
566
567 public void Execute()
568 {
569 unsafe
570 {
571 using (var container = new NativeParallelHashSet<int>(1, Allocator->Handle))
572 {
573 }
574 }
575 }
576 }
577
578 [Test]
579 public unsafe void NativeParallelHashSet_BurstedCustomAllocatorTest()
580 {
581 AllocatorManager.Initialize();
582 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
583 ref var allocator = ref allocatorHelper.Allocator;
584 allocator.Initialize();
585
586 var allocatorPtr = (CustomAllocatorTests.CountingAllocator*)UnsafeUtility.AddressOf<CustomAllocatorTests.CountingAllocator>(ref allocator);
587 unsafe
588 {
589 var handle = new BurstedCustomAllocatorJob {Allocator = allocatorPtr }.Schedule();
590 handle.Complete();
591 }
592
593 FastAssert.IsTrue(allocator.WasUsed);
594 allocator.Dispose();
595 allocatorHelper.Dispose();
596 AllocatorManager.Shutdown();
597 }
598}