A game about forced loneliness, made by TACStudios
1using NUnit.Framework;
2using Unity.Collections;
3using Unity.Jobs;
4using Unity.Collections.LowLevel.Unsafe;
5using Unity.Collections.LowLevel.Unsafe.NotBurstCompatible;
6using Unity.Collections.Tests;
7using System;
8using Unity.Burst;
9using System.Diagnostics;
10
11internal class UnsafeHashMapTests : CollectionsTestCommonBase
12{
13 [Test]
14 public void UnsafeHashMap_ForEach([Values(10, 1000)] int n)
15 {
16 var seen = new NativeArray<int>(n, Allocator.Temp);
17 using (var container = new UnsafeHashMap<int, int>(32, CommonRwdAllocator.Handle))
18 {
19 for (int i = 0; i < n; i++)
20 {
21 container.Add(i, i * 37);
22 }
23
24 var count = 0;
25 foreach (var kv in container)
26 {
27 int value;
28 Assert.True(container.TryGetValue(kv.Key, out value));
29 Assert.AreEqual(value, kv.Value);
30 Assert.AreEqual(kv.Key * 37, kv.Value);
31
32 seen[kv.Key] = seen[kv.Key] + 1;
33 ++count;
34 }
35
36 Assert.AreEqual(container.Count, count);
37 for (int i = 0; i < n; i++)
38 {
39 Assert.AreEqual(1, seen[i], $"Incorrect key count {i}");
40 }
41 }
42 }
43
44 [Test]
45 public void UnsafeHashMap_ForEach_FixedStringKey()
46 {
47 using (var stringList = new NativeList<FixedString32Bytes>(10, Allocator.Persistent) { "Hello", ",", "World", "!" })
48 {
49 var seen = new NativeArray<int>(stringList.Length, Allocator.Temp);
50 var container = new UnsafeHashMap<FixedString128Bytes, float>(50, Allocator.Temp);
51 foreach (var str in stringList)
52 {
53 container.Add(str, 0);
54 }
55
56 foreach (var pair in container)
57 {
58 int index = stringList.IndexOf(pair.Key);
59 Assert.AreEqual(stringList[index], pair.Key.ToString());
60 seen[index] = seen[index] + 1;
61 }
62
63 for (int i = 0; i < stringList.Length; i++)
64 {
65 Assert.AreEqual(1, seen[i], $"Incorrect value count {stringList[i]}");
66 }
67 }
68 }
69
70 struct UnsafeHashMap_ForEachIterator : IJob
71 {
72 [ReadOnly]
73 public UnsafeHashMap<int, int>.Enumerator Iter;
74
75 public void Execute()
76 {
77 while (Iter.MoveNext())
78 {
79 }
80 }
81 }
82
83 [Test]
84 public void UnsafeHashMap_ForEach_Throws_Job_Iterator()
85 {
86 using (var container = new UnsafeHashMap<int, int>(32, CommonRwdAllocator.Handle))
87 {
88 var jobHandle = new UnsafeHashMap_ForEachIterator
89 {
90 Iter = container.GetEnumerator()
91
92 }.Schedule();
93
94 jobHandle.Complete();
95 }
96 }
97
98 struct UnsafeHashMap_ForEach_Job : IJob
99 {
100 public UnsafeHashMap<int, int>.ReadOnly Input;
101
102 [ReadOnly]
103 public int Num;
104
105 public void Execute()
106 {
107 var seen = new NativeArray<int>(Num, Allocator.Temp);
108
109 var count = 0;
110 foreach (var kv in Input)
111 {
112 int value;
113 Assert.True(Input.TryGetValue(kv.Key, out value));
114 Assert.AreEqual(value, kv.Value);
115 Assert.AreEqual(kv.Key * 37, kv.Value);
116
117 seen[kv.Key] = seen[kv.Key] + 1;
118 ++count;
119 }
120
121 Assert.AreEqual(Input.Count, count);
122 for (int i = 0; i < Num; i++)
123 {
124 Assert.AreEqual(1, seen[i], $"Incorrect key count {i}");
125 }
126
127 seen.Dispose();
128 }
129 }
130
131 [Test]
132 public void UnsafeHashMap_ForEach_From_Job([Values(10, 1000)] int n)
133 {
134 var seen = new NativeArray<int>(n, Allocator.Temp);
135 using (var container = new UnsafeHashMap<int, int>(32, CommonRwdAllocator.Handle))
136 {
137 for (int i = 0; i < n; i++)
138 {
139 container.Add(i, i * 37);
140 }
141
142 new UnsafeHashMap_ForEach_Job
143 {
144 Input = container.AsReadOnly(),
145 Num = n,
146
147 }.Run();
148 }
149 }
150
151 [Test]
152 public void UnsafeHashMap_EnumeratorDoesNotReturnRemovedElementsTest()
153 {
154 UnsafeHashMap<int, int> container = new UnsafeHashMap<int, int>(5, Allocator.Temp);
155 for (int i = 0; i < 5; i++)
156 {
157 container.Add(i, i);
158 }
159
160 int elementToRemove = 2;
161 Assert.IsTrue(container.Remove(elementToRemove));
162 Assert.IsFalse(container.Remove(elementToRemove));
163
164 using (var enumerator = container.GetEnumerator())
165 {
166 while (enumerator.MoveNext())
167 {
168 Assert.AreNotEqual(elementToRemove, enumerator.Current.Key);
169 }
170 }
171
172 container.Dispose();
173 }
174
175 [Test]
176 public void UnsafeHashMap_EnumeratorInfiniteIterationTest()
177 {
178 UnsafeHashMap<int, int> container = new UnsafeHashMap<int, int>(5, Allocator.Temp);
179 for (int i = 0; i < 5; i++)
180 {
181 container.Add(i, i);
182 }
183
184 for (int i = 0; i < 2; i++)
185 {
186 Assert.IsTrue(container.Remove(i));
187 }
188
189 var expected = container.Count;
190 int count = 0;
191 using (var enumerator = container.GetEnumerator())
192 {
193 while (enumerator.MoveNext())
194 {
195 if (count++ > expected)
196 {
197 break;
198 }
199 }
200 }
201
202 Assert.AreEqual(expected, count);
203 container.Dispose();
204 }
205
206 [Test]
207 public void UnsafeHashMap_CustomAllocatorTest()
208 {
209 AllocatorManager.Initialize();
210 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
211 ref var allocator = ref allocatorHelper.Allocator;
212 allocator.Initialize();
213
214 using (var container = new UnsafeHashMap<int, int>(1, allocator.Handle))
215 {
216 }
217
218 Assert.IsTrue(allocator.WasUsed);
219 allocator.Dispose();
220 allocatorHelper.Dispose();
221 AllocatorManager.Shutdown();
222 }
223
224 [BurstCompile]
225 struct BurstedCustomAllocatorJob : IJob
226 {
227 [NativeDisableUnsafePtrRestriction]
228 public unsafe CustomAllocatorTests.CountingAllocator* Allocator;
229
230 public void Execute()
231 {
232 unsafe
233 {
234 using (var container = new UnsafeHashMap<int, int>(1, Allocator->Handle))
235 {
236 }
237 }
238 }
239 }
240
241 [Test]
242 public unsafe void UnsafeHashMap_BurstedCustomAllocatorTest()
243 {
244 AllocatorManager.Initialize();
245 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
246 ref var allocator = ref allocatorHelper.Allocator;
247 allocator.Initialize();
248
249 var allocatorPtr = (CustomAllocatorTests.CountingAllocator*)UnsafeUtility.AddressOf<CustomAllocatorTests.CountingAllocator>(ref allocator);
250 unsafe
251 {
252 var handle = new BurstedCustomAllocatorJob { Allocator = allocatorPtr }.Schedule();
253 handle.Complete();
254 }
255
256 Assert.IsTrue(allocator.WasUsed);
257 allocator.Dispose();
258 allocatorHelper.Dispose();
259 AllocatorManager.Shutdown();
260 }
261
262 static void ExpectedCount<TKey, TValue>(ref UnsafeHashMap<TKey, TValue> container, int expected)
263 where TKey : unmanaged, IEquatable<TKey>
264 where TValue : unmanaged
265 {
266 Assert.AreEqual(expected == 0, container.IsEmpty);
267 Assert.AreEqual(expected, container.Count);
268 }
269
270 [Test]
271 public void UnsafeHashMap_TryAdd_TryGetValue_Clear()
272 {
273 var container = new UnsafeHashMap<int, int>(16, Allocator.Temp);
274 ExpectedCount(ref container, 0);
275
276 int iSquared;
277 // Make sure GetValue fails if container is empty
278 Assert.IsFalse(container.TryGetValue(0, out iSquared), "TryGetValue on empty container did not fail");
279
280 // Make sure inserting values work
281 for (int i = 0; i < 16; ++i)
282 Assert.IsTrue(container.TryAdd(i, i * i), "Failed to add value");
283 ExpectedCount(ref container, 16);
284
285 // Make sure inserting duplicate keys fails
286 for (int i = 0; i < 16; ++i)
287 Assert.IsFalse(container.TryAdd(i, i), "Adding duplicate keys did not fail");
288 ExpectedCount(ref container, 16);
289
290 // Make sure reading the inserted values work
291 for (int i = 0; i < 16; ++i)
292 {
293 Assert.IsTrue(container.TryGetValue(i, out iSquared), "Failed get value from hash table");
294 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table");
295 }
296
297 // Make sure clearing removes all keys
298 container.Clear();
299 ExpectedCount(ref container, 0);
300
301 for (int i = 0; i < 16; ++i)
302 Assert.IsFalse(container.TryGetValue(i, out iSquared), "Got value from hash table after clearing");
303
304 container.Dispose();
305 }
306
307 [Test]
308 public void UnsafeHashMap_Key_Collisions()
309 {
310 var container = new UnsafeHashMap<int, int>(16, Allocator.Temp);
311 int iSquared;
312
313 // Make sure GetValue fails if container is empty
314 Assert.IsFalse(container.TryGetValue(0, out iSquared), "TryGetValue on empty container did not fail");
315
316 // Make sure inserting values work
317 for (int i = 0; i < 8; ++i)
318 {
319 Assert.IsTrue(container.TryAdd(i, i * i), "Failed to add value");
320 }
321
322 // The bucket size is capacity * 2, adding that number should result in hash collisions
323 for (int i = 0; i < 8; ++i)
324 {
325 Assert.IsTrue(container.TryAdd(i + 32, i), "Failed to add value with potential hash collision");
326 }
327
328 // Make sure reading the inserted values work
329 for (int i = 0; i < 8; ++i)
330 {
331 Assert.IsTrue(container.TryGetValue(i, out iSquared), "Failed get value from hash table");
332 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table");
333 }
334
335 for (int i = 0; i < 8; ++i)
336 {
337 Assert.IsTrue(container.TryGetValue(i + 32, out iSquared), "Failed get value from hash table");
338 Assert.AreEqual(iSquared, i, "Got the wrong value from the hash table");
339 }
340
341 container.Dispose();
342 }
343
344 [Test]
345 public void UnsafeHashMap_SupportsAutomaticCapacityChange()
346 {
347 var container = new UnsafeHashMap<int, int>(1, Allocator.Temp);
348 int iSquared;
349
350 // Make sure inserting values work and grows the capacity
351 for (int i = 0; i < 8; ++i)
352 {
353 Assert.IsTrue(container.TryAdd(i, i * i), "Failed to add value");
354 }
355 Assert.IsTrue(container.Capacity >= 8, "Capacity was not updated correctly");
356
357 // Make sure reading the inserted values work
358 for (int i = 0; i < 8; ++i)
359 {
360 Assert.IsTrue(container.TryGetValue(i, out iSquared), "Failed get value from hash table");
361 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table");
362 }
363
364 container.Dispose();
365 }
366
367 [Test]
368 [TestRequiresDotsDebugOrCollectionChecks]
369 public void UnsafeHashMap_SameKey()
370 {
371 using (var container = new UnsafeHashMap<int, int>(0, Allocator.Persistent))
372 {
373 Assert.DoesNotThrow(() => container.Add(0, 0));
374 Assert.Throws<ArgumentException>(() => container.Add(0, 0));
375 }
376
377 using (var container = new UnsafeHashMap<int, int>(0, Allocator.Persistent))
378 {
379 Assert.IsTrue(container.TryAdd(0, 0));
380 Assert.IsFalse(container.TryAdd(0, 0));
381 }
382 }
383
384 [Test]
385 public void UnsafeHashMap_IsEmpty()
386 {
387 var container = new UnsafeHashMap<int, int>(0, Allocator.Persistent);
388 Assert.IsTrue(container.IsEmpty);
389
390 container.TryAdd(0, 0);
391 Assert.IsFalse(container.IsEmpty);
392 ExpectedCount(ref container, 1);
393
394 Assert.IsTrue(container.Remove(0));
395 Assert.IsFalse(container.Remove(0));
396 Assert.IsTrue(container.IsEmpty);
397
398 container.TryAdd(0, 0);
399 container.Clear();
400 Assert.IsTrue(container.IsEmpty);
401
402 container.Dispose();
403 }
404
405 [Test]
406 public void UnsafeHashMap_EmptyCapacity()
407 {
408 var container = new UnsafeHashMap<int, int>(0, Allocator.Persistent);
409 container.TryAdd(0, 0);
410 ExpectedCount(ref container, 1);
411 container.Dispose();
412 }
413
414 [Test]
415 public void UnsafeHashMap_Remove()
416 {
417 var container = new UnsafeHashMap<int, int>(8, Allocator.Temp);
418 int iSquared;
419
420 for (int rm = 0; rm < 8; ++rm)
421 {
422 Assert.IsFalse(container.Remove(rm));
423 }
424
425 // Make sure inserting values work
426 for (int i = 0; i < 8; ++i)
427 {
428 Assert.IsTrue(container.TryAdd(i, i * i), "Failed to add value");
429 }
430 Assert.AreEqual(8, container.Count);
431
432 // Make sure reading the inserted values work
433 for (int i = 0; i < 8; ++i)
434 {
435 Assert.IsTrue(container.TryGetValue(i, out iSquared), "Failed get value from hash table");
436 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table");
437 }
438
439 for (int rm = 0; rm < 8; ++rm)
440 {
441 Assert.IsTrue(container.Remove(rm));
442 Assert.IsFalse(container.TryGetValue(rm, out iSquared), "Failed to remove value from hash table");
443 for (int i = rm + 1; i < 8; ++i)
444 {
445 Assert.IsTrue(container.TryGetValue(i, out iSquared), "Failed get value from hash table");
446 Assert.AreEqual(iSquared, i * i, "Got the wrong value from the hash table");
447 }
448 }
449 Assert.AreEqual(0, container.Count);
450
451 // Make sure entries were freed
452 for (int i = 0; i < 8; ++i)
453 {
454 Assert.IsTrue(container.TryAdd(i, i * i), "Failed to add value");
455 }
456
457 Assert.AreEqual(8, container.Count);
458 container.Dispose();
459 }
460
461 [Test]
462 public void UnsafeHashMap_RemoveOnEmptyMap_DoesNotThrow()
463 {
464 var container = new UnsafeHashMap<int, int>(0, Allocator.Temp);
465 Assert.DoesNotThrow(() => container.Remove(0));
466 Assert.DoesNotThrow(() => container.Remove(-425196));
467 container.Dispose();
468 }
469
470 [Test]
471 public void UnsafeHashMap_TryAddScalability()
472 {
473 var container = new UnsafeHashMap<int, int>(1, Allocator.Persistent);
474 Assert.AreEqual(container.Capacity, (1 << container.m_Data.Log2MinGrowth));
475 for (int i = 0; i != 1000 * 100; i++)
476 {
477 container.Add(i, i);
478 }
479
480 int value;
481 Assert.AreEqual(container.Count, 100000);
482 Assert.IsFalse(container.TryGetValue(-1, out value));
483 Assert.IsFalse(container.TryGetValue(1000 * 1000, out value));
484
485 for (int i = 0; i != 1000 * 100; i++)
486 {
487 Assert.IsTrue(container.TryGetValue(i, out value));
488 Assert.AreEqual(i, value);
489 }
490
491 container.Dispose();
492 }
493
494 [Test]
495 public unsafe void UnsafeHashMap_TrimExcess()
496 {
497 using (var container = new UnsafeHashMap<int, int>(1024, Allocator.Persistent))
498 {
499 var oldCapacity = container.Capacity;
500
501 container.Add(123, 345);
502 container.TrimExcess();
503 Assert.AreEqual(container.Capacity, (1 << container.m_Data.Log2MinGrowth));
504 Assert.AreEqual(1, container.Count);
505 Assert.AreNotEqual(oldCapacity, container.Capacity);
506
507 oldCapacity = container.Capacity;
508
509 container.Remove(123);
510 Assert.AreEqual(container.Count, 0);
511 container.TrimExcess();
512 Assert.AreEqual(oldCapacity, container.Capacity);
513
514 container.Add(123, 345);
515 Assert.AreEqual(container.Count, 1);
516 Assert.AreEqual(oldCapacity, container.Capacity);
517
518 container.Clear();
519 Assert.AreEqual(container.Count, 0);
520 Assert.AreEqual(oldCapacity, container.Capacity);
521 }
522 }
523
524 [Test]
525 public void UnsafeHashMap_IndexerAdd_ResizesContainer()
526 {
527 var container = new UnsafeHashMap<int, int>(8, Allocator.Persistent);
528 for (int i = 0; i < 1024; i++)
529 {
530 container[i] = i;
531 }
532 Assert.AreEqual(1024, container.Count);
533 container.Dispose();
534 }
535}