Geen omschrijving
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NativeHashMapTests.cs 31KB

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