Без опису
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

NativeHashMapTests.cs 29KB

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