No Description
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.

NativeParallelHashMapTests.cs 30KB

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