Ei kuvausta
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.

UnsafeParallelHashMap.cs 68KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Runtime.InteropServices;
  6. using System.Threading;
  7. using Unity.Burst;
  8. using Unity.Mathematics;
  9. using Unity.Jobs;
  10. using Unity.Jobs.LowLevel.Unsafe;
  11. using UnityEngine.Assertions;
  12. using System.Runtime.CompilerServices;
  13. namespace Unity.Collections.LowLevel.Unsafe
  14. {
  15. /// <summary>
  16. /// A bucket of key-value pairs. Used as the internal storage for hash maps.
  17. /// </summary>
  18. /// <remarks>Exposed publicly only for advanced use cases.</remarks>
  19. [GenerateTestsForBurstCompatibility]
  20. public unsafe struct UnsafeParallelHashMapBucketData
  21. {
  22. internal UnsafeParallelHashMapBucketData(byte* v, byte* k, byte* n, byte* b, int bcm)
  23. {
  24. values = v;
  25. keys = k;
  26. next = n;
  27. buckets = b;
  28. bucketCapacityMask = bcm;
  29. }
  30. /// <summary>
  31. /// The buffer of values.
  32. /// </summary>
  33. /// <value>The buffer of values.</value>
  34. public readonly byte* values;
  35. /// <summary>
  36. /// The buffer of keys.
  37. /// </summary>
  38. /// <value>The buffer of keys.</value>
  39. public readonly byte* keys;
  40. /// <summary>
  41. /// The next bucket in the chain.
  42. /// </summary>
  43. /// <value>The next bucket in the chain.</value>
  44. public readonly byte* next;
  45. /// <summary>
  46. /// The first bucket in the chain.
  47. /// </summary>
  48. /// <value>The first bucket in the chain.</value>
  49. public readonly byte* buckets;
  50. /// <summary>
  51. /// One less than the bucket capacity.
  52. /// </summary>
  53. /// <value>One less than the bucket capacity.</value>
  54. public readonly int bucketCapacityMask;
  55. }
  56. [StructLayout(LayoutKind.Explicit)]
  57. [GenerateTestsForBurstCompatibility]
  58. internal unsafe struct UnsafeParallelHashMapData
  59. {
  60. [FieldOffset(0)]
  61. internal byte* values;
  62. // 4-byte padding on 32-bit architectures here
  63. [FieldOffset(8)]
  64. internal byte* keys;
  65. // 4-byte padding on 32-bit architectures here
  66. [FieldOffset(16)]
  67. internal byte* next;
  68. // 4-byte padding on 32-bit architectures here
  69. [FieldOffset(24)]
  70. internal byte* buckets;
  71. // 4-byte padding on 32-bit architectures here
  72. [FieldOffset(32)]
  73. internal int keyCapacity;
  74. [FieldOffset(36)]
  75. internal int bucketCapacityMask; // = bucket capacity - 1
  76. [FieldOffset(40)]
  77. internal int allocatedIndexLength;
  78. #if UNITY_2022_2_14F1_OR_NEWER
  79. const int kFirstFreeTLSOffset = JobsUtility.CacheLineSize < 64 ? 64 : JobsUtility.CacheLineSize;
  80. internal int* firstFreeTLS => (int*)((byte*)UnsafeUtility.AddressOf(ref this) + kFirstFreeTLSOffset);
  81. #else
  82. [FieldOffset(JobsUtility.CacheLineSize < 64 ? 64 : JobsUtility.CacheLineSize)]
  83. internal fixed int firstFreeTLS[JobsUtility.MaxJobThreadCount * IntsPerCacheLine];
  84. #endif
  85. // 64 is the cache line size on x86, arm usually has 32 - so it is possible to save some memory there
  86. internal const int IntsPerCacheLine = JobsUtility.CacheLineSize / sizeof(int);
  87. internal static int GetBucketSize(int capacity)
  88. {
  89. return capacity * 2;
  90. }
  91. internal static int GrowCapacity(int capacity)
  92. {
  93. if (capacity == 0)
  94. {
  95. return 1;
  96. }
  97. return capacity * 2;
  98. }
  99. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  100. internal static void AllocateHashMap<TKey, TValue>(int length, int bucketLength, AllocatorManager.AllocatorHandle label,
  101. out UnsafeParallelHashMapData* outBuf)
  102. where TKey : unmanaged
  103. where TValue : unmanaged
  104. {
  105. #if UNITY_2022_2_14F1_OR_NEWER
  106. int maxThreadCount = JobsUtility.ThreadIndexCount;
  107. // Calculate the size of UnsafeParallelHashMapData since we need to account for how many
  108. // jow worker threads the runtime has available. -1 since UnsafeParallelHashMapData.firstFreeTLS accounts for 1 int already
  109. Assert.IsTrue(sizeof(UnsafeParallelHashMapData) <= kFirstFreeTLSOffset);
  110. int hashMapDataSize = kFirstFreeTLSOffset + (sizeof(int) * IntsPerCacheLine * maxThreadCount);
  111. #else
  112. int hashMapDataSize = sizeof(UnsafeParallelHashMapData);
  113. #endif
  114. UnsafeParallelHashMapData* data = (UnsafeParallelHashMapData*)Memory.Unmanaged.Allocate(hashMapDataSize, JobsUtility.CacheLineSize, label);
  115. bucketLength = math.ceilpow2(bucketLength);
  116. data->keyCapacity = length;
  117. data->bucketCapacityMask = bucketLength - 1;
  118. int keyOffset, nextOffset, bucketOffset;
  119. int totalSize = CalculateDataSize<TKey, TValue>(length, bucketLength, out keyOffset, out nextOffset, out bucketOffset);
  120. data->values = (byte*)Memory.Unmanaged.Allocate(totalSize, JobsUtility.CacheLineSize, label);
  121. data->keys = data->values + keyOffset;
  122. data->next = data->values + nextOffset;
  123. data->buckets = data->values + bucketOffset;
  124. outBuf = data;
  125. }
  126. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  127. internal static void ReallocateHashMap<TKey, TValue>(UnsafeParallelHashMapData* data, int newCapacity, int newBucketCapacity, AllocatorManager.AllocatorHandle label)
  128. where TKey : unmanaged
  129. where TValue : unmanaged
  130. {
  131. newBucketCapacity = math.ceilpow2(newBucketCapacity);
  132. if (data->keyCapacity == newCapacity && (data->bucketCapacityMask + 1) == newBucketCapacity)
  133. {
  134. return;
  135. }
  136. CheckHashMapReallocateDoesNotShrink(data, newCapacity);
  137. int keyOffset, nextOffset, bucketOffset;
  138. int totalSize = CalculateDataSize<TKey, TValue>(newCapacity, newBucketCapacity, out keyOffset, out nextOffset, out bucketOffset);
  139. byte* newData = (byte*)Memory.Unmanaged.Allocate(totalSize, JobsUtility.CacheLineSize, label);
  140. byte* newKeys = newData + keyOffset;
  141. byte* newNext = newData + nextOffset;
  142. byte* newBuckets = newData + bucketOffset;
  143. // The items are taken from a free-list and might not be tightly packed, copy all of the old capcity
  144. UnsafeUtility.MemCpy(newData, data->values, data->keyCapacity * UnsafeUtility.SizeOf<TValue>());
  145. UnsafeUtility.MemCpy(newKeys, data->keys, data->keyCapacity * UnsafeUtility.SizeOf<TKey>());
  146. UnsafeUtility.MemCpy(newNext, data->next, data->keyCapacity * UnsafeUtility.SizeOf<int>());
  147. for (int emptyNext = data->keyCapacity; emptyNext < newCapacity; ++emptyNext)
  148. {
  149. ((int*)newNext)[emptyNext] = -1;
  150. }
  151. // re-hash the buckets, first clear the new bucket list, then insert all values from the old list
  152. for (int bucket = 0; bucket < newBucketCapacity; ++bucket)
  153. {
  154. ((int*)newBuckets)[bucket] = -1;
  155. }
  156. for (int bucket = 0; bucket <= data->bucketCapacityMask; ++bucket)
  157. {
  158. int* buckets = (int*)data->buckets;
  159. int* nextPtrs = (int*)newNext;
  160. while (buckets[bucket] >= 0)
  161. {
  162. int curEntry = buckets[bucket];
  163. buckets[bucket] = nextPtrs[curEntry];
  164. int newBucket = UnsafeUtility.ReadArrayElement<TKey>(data->keys, curEntry).GetHashCode() & (newBucketCapacity - 1);
  165. nextPtrs[curEntry] = ((int*)newBuckets)[newBucket];
  166. ((int*)newBuckets)[newBucket] = curEntry;
  167. }
  168. }
  169. Memory.Unmanaged.Free(data->values, label);
  170. if (data->allocatedIndexLength > data->keyCapacity)
  171. {
  172. data->allocatedIndexLength = data->keyCapacity;
  173. }
  174. data->values = newData;
  175. data->keys = newKeys;
  176. data->next = newNext;
  177. data->buckets = newBuckets;
  178. data->keyCapacity = newCapacity;
  179. data->bucketCapacityMask = newBucketCapacity - 1;
  180. }
  181. internal static void DeallocateHashMap(UnsafeParallelHashMapData* data, AllocatorManager.AllocatorHandle allocator)
  182. {
  183. Memory.Unmanaged.Free(data->values, allocator);
  184. Memory.Unmanaged.Free(data, allocator);
  185. }
  186. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  187. internal static int CalculateDataSize<TKey, TValue>(int length, int bucketLength, out int keyOffset, out int nextOffset, out int bucketOffset)
  188. where TKey : unmanaged
  189. where TValue : unmanaged
  190. {
  191. var sizeOfTValue = UnsafeUtility.SizeOf<TValue>();
  192. var sizeOfTKey = UnsafeUtility.SizeOf<TKey>();
  193. var sizeOfInt = UnsafeUtility.SizeOf<int>();
  194. var valuesSize = CollectionHelper.Align(sizeOfTValue * length, JobsUtility.CacheLineSize);
  195. var keysSize = CollectionHelper.Align(sizeOfTKey * length, JobsUtility.CacheLineSize);
  196. var nextSize = CollectionHelper.Align(sizeOfInt * length, JobsUtility.CacheLineSize);
  197. var bucketSize = CollectionHelper.Align(sizeOfInt * bucketLength, JobsUtility.CacheLineSize);
  198. var totalSize = valuesSize + keysSize + nextSize + bucketSize;
  199. keyOffset = 0 + valuesSize;
  200. nextOffset = keyOffset + keysSize;
  201. bucketOffset = nextOffset + nextSize;
  202. return totalSize;
  203. }
  204. internal static bool IsEmpty(UnsafeParallelHashMapData* data)
  205. {
  206. if (data->allocatedIndexLength <= 0)
  207. {
  208. return true;
  209. }
  210. var bucketArray = (int*)data->buckets;
  211. var bucketNext = (int*)data->next;
  212. var capacityMask = data->bucketCapacityMask;
  213. for (int i = 0; i <= capacityMask; ++i)
  214. {
  215. int bucket = bucketArray[i];
  216. if (bucket != -1)
  217. {
  218. return false;
  219. }
  220. }
  221. return true;
  222. }
  223. internal static int GetCount(UnsafeParallelHashMapData* data)
  224. {
  225. if (data->allocatedIndexLength <= 0)
  226. {
  227. return 0;
  228. }
  229. var bucketNext = (int*)data->next;
  230. var freeListSize = 0;
  231. #if UNITY_2022_2_14F1_OR_NEWER
  232. int maxThreadCount = JobsUtility.ThreadIndexCount;
  233. #else
  234. int maxThreadCount = JobsUtility.MaxJobThreadCount;
  235. #endif
  236. for (int tls = 0; tls < maxThreadCount; ++tls)
  237. {
  238. for (var freeIdx = data->firstFreeTLS[tls * IntsPerCacheLine]
  239. ; freeIdx >= 0
  240. ; freeIdx = bucketNext[freeIdx]
  241. )
  242. {
  243. ++freeListSize;
  244. }
  245. }
  246. return math.min(data->keyCapacity, data->allocatedIndexLength) - freeListSize;
  247. }
  248. internal static bool MoveNextSearch(UnsafeParallelHashMapData* data, ref int bucketIndex, ref int nextIndex, out int index)
  249. {
  250. var bucketArray = (int*)data->buckets;
  251. var capacityMask = data->bucketCapacityMask;
  252. for (int i = bucketIndex; i <= capacityMask; ++i)
  253. {
  254. var idx = bucketArray[i];
  255. if (idx != -1)
  256. {
  257. var bucketNext = (int*)data->next;
  258. index = idx;
  259. bucketIndex = i + 1;
  260. nextIndex = bucketNext[idx];
  261. return true;
  262. }
  263. }
  264. index = -1;
  265. bucketIndex = capacityMask + 1;
  266. nextIndex = -1;
  267. return false;
  268. }
  269. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  270. internal static bool MoveNext(UnsafeParallelHashMapData* data, ref int bucketIndex, ref int nextIndex, out int index)
  271. {
  272. if (nextIndex != -1)
  273. {
  274. var bucketNext = (int*)data->next;
  275. index = nextIndex;
  276. nextIndex = bucketNext[nextIndex];
  277. return true;
  278. }
  279. return MoveNextSearch(data, ref bucketIndex, ref nextIndex, out index);
  280. }
  281. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
  282. internal static void GetKeyArray<TKey>(UnsafeParallelHashMapData* data, NativeArray<TKey> result)
  283. where TKey : unmanaged
  284. {
  285. var bucketArray = (int*)data->buckets;
  286. var bucketNext = (int*)data->next;
  287. for (int i = 0, count = 0, max = result.Length; i <= data->bucketCapacityMask && count < max; ++i)
  288. {
  289. int bucket = bucketArray[i];
  290. while (bucket != -1)
  291. {
  292. result[count++] = UnsafeUtility.ReadArrayElement<TKey>(data->keys, bucket);
  293. bucket = bucketNext[bucket];
  294. }
  295. }
  296. }
  297. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
  298. internal static void GetValueArray<TValue>(UnsafeParallelHashMapData* data, NativeArray<TValue> result)
  299. where TValue : unmanaged
  300. {
  301. var bucketArray = (int*)data->buckets;
  302. var bucketNext = (int*)data->next;
  303. for (int i = 0, count = 0, max = result.Length, capacityMask = data->bucketCapacityMask
  304. ; i <= capacityMask && count < max
  305. ; ++i
  306. )
  307. {
  308. int bucket = bucketArray[i];
  309. while (bucket != -1)
  310. {
  311. result[count++] = UnsafeUtility.ReadArrayElement<TValue>(data->values, bucket);
  312. bucket = bucketNext[bucket];
  313. }
  314. }
  315. }
  316. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  317. internal static void GetKeyValueArrays<TKey, TValue>(UnsafeParallelHashMapData* data, NativeKeyValueArrays<TKey, TValue> result)
  318. where TKey : unmanaged
  319. where TValue : unmanaged
  320. {
  321. var bucketArray = (int*)data->buckets;
  322. var bucketNext = (int*)data->next;
  323. for (int i = 0, count = 0, max = result.Length, capacityMask = data->bucketCapacityMask
  324. ; i <= capacityMask && count < max
  325. ; ++i
  326. )
  327. {
  328. int bucket = bucketArray[i];
  329. while (bucket != -1)
  330. {
  331. result.Keys[count] = UnsafeUtility.ReadArrayElement<TKey>(data->keys, bucket);
  332. result.Values[count] = UnsafeUtility.ReadArrayElement<TValue>(data->values, bucket);
  333. count++;
  334. bucket = bucketNext[bucket];
  335. }
  336. }
  337. }
  338. internal UnsafeParallelHashMapBucketData GetBucketData()
  339. {
  340. return new UnsafeParallelHashMapBucketData(values, keys, next, buckets, bucketCapacityMask);
  341. }
  342. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  343. static void CheckHashMapReallocateDoesNotShrink(UnsafeParallelHashMapData* data, int newCapacity)
  344. {
  345. if (data->keyCapacity > newCapacity)
  346. throw new InvalidOperationException("Shrinking a hash map is not supported");
  347. }
  348. }
  349. [NativeContainer]
  350. [GenerateTestsForBurstCompatibility]
  351. internal unsafe struct UnsafeParallelHashMapDataDispose
  352. {
  353. [NativeDisableUnsafePtrRestriction]
  354. internal UnsafeParallelHashMapData* m_Buffer;
  355. internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
  356. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  357. internal AtomicSafetyHandle m_Safety;
  358. #endif
  359. public void Dispose()
  360. {
  361. UnsafeParallelHashMapData.DeallocateHashMap(m_Buffer, m_AllocatorLabel);
  362. }
  363. }
  364. [BurstCompile]
  365. internal unsafe struct UnsafeParallelHashMapDataDisposeJob : IJob
  366. {
  367. internal UnsafeParallelHashMapDataDispose Data;
  368. public void Execute()
  369. {
  370. Data.Dispose();
  371. }
  372. }
  373. [StructLayout(LayoutKind.Sequential)]
  374. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  375. internal struct UnsafeParallelHashMapBase<TKey, TValue>
  376. where TKey : unmanaged, IEquatable<TKey>
  377. where TValue : unmanaged
  378. {
  379. internal static unsafe void Clear(UnsafeParallelHashMapData* data)
  380. {
  381. UnsafeUtility.MemSet(data->buckets, 0xff, (data->bucketCapacityMask + 1) * 4);
  382. UnsafeUtility.MemSet(data->next, 0xff, (data->keyCapacity) * 4);
  383. #if UNITY_2022_2_14F1_OR_NEWER
  384. int maxThreadCount = JobsUtility.ThreadIndexCount;
  385. #else
  386. int maxThreadCount = JobsUtility.MaxJobThreadCount;
  387. #endif
  388. for (int tls = 0; tls < maxThreadCount; ++tls)
  389. {
  390. data->firstFreeTLS[tls * UnsafeParallelHashMapData.IntsPerCacheLine] = -1;
  391. }
  392. data->allocatedIndexLength = 0;
  393. }
  394. private const int SentinelRefilling = -2;
  395. private const int SentinelSwapInProgress = -3;
  396. internal static unsafe int AllocEntry(UnsafeParallelHashMapData* data, int threadIndex)
  397. {
  398. int idx;
  399. int* nextPtrs = (int*)data->next;
  400. do
  401. {
  402. do
  403. {
  404. idx = Volatile.Read(ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine]);
  405. } while (idx == SentinelSwapInProgress);
  406. // Check if this thread has a free entry. Negative value means there is nothing free.
  407. if (idx < 0)
  408. {
  409. // Try to refill local cache. The local cache is a linked list of 16 free entries.
  410. // Indicate to other threads that we are refilling the cache.
  411. // -2 means refilling cache.
  412. // -1 means nothing free on this thread.
  413. Interlocked.Exchange(ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine], SentinelRefilling);
  414. // If it failed try to get one from the never-allocated array
  415. if (data->allocatedIndexLength < data->keyCapacity)
  416. {
  417. idx = Interlocked.Add(ref data->allocatedIndexLength, 16) - 16;
  418. if (idx < data->keyCapacity - 1)
  419. {
  420. int count = math.min(16, data->keyCapacity - idx);
  421. // Set up a linked list of free entries.
  422. for (int i = 1; i < count; ++i)
  423. {
  424. nextPtrs[idx + i] = idx + i + 1;
  425. }
  426. // Last entry points to null.
  427. nextPtrs[idx + count - 1] = -1;
  428. // The first entry is going to be allocated to someone so it also points to null.
  429. nextPtrs[idx] = -1;
  430. // Set the TLS first free to the head of the list, which is the one after the entry we are returning.
  431. Interlocked.Exchange(ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine], idx + 1);
  432. return idx;
  433. }
  434. if (idx == data->keyCapacity - 1)
  435. {
  436. // We tried to allocate more entries for this thread but we've already hit the key capacity,
  437. // so we are in fact out of space. Record that this thread has no more entries.
  438. Interlocked.Exchange(ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine], -1);
  439. return idx;
  440. }
  441. }
  442. // If we reach here, then we couldn't allocate more entries for this thread, so it's completely empty.
  443. Interlocked.Exchange(ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine], -1);
  444. #if UNITY_2022_2_14F1_OR_NEWER
  445. int maxThreadCount = JobsUtility.ThreadIndexCount;
  446. #else
  447. int maxThreadCount = JobsUtility.MaxJobThreadCount;
  448. #endif
  449. // Failed to get any, try to get one from another free list
  450. bool again = true;
  451. while (again)
  452. {
  453. again = false;
  454. for (int other = (threadIndex + 1) % maxThreadCount
  455. ; other != threadIndex
  456. ; other = (other + 1) % maxThreadCount
  457. )
  458. {
  459. // Attempt to grab a free entry from another thread and switch the other thread's free head
  460. // atomically.
  461. do
  462. {
  463. do
  464. {
  465. idx = Volatile.Read(ref data->firstFreeTLS[other * UnsafeParallelHashMapData.IntsPerCacheLine]);
  466. } while (idx == SentinelSwapInProgress);
  467. if (idx < 0)
  468. {
  469. break;
  470. }
  471. }
  472. while (Interlocked.CompareExchange(
  473. ref data->firstFreeTLS[other * UnsafeParallelHashMapData.IntsPerCacheLine]
  474. , SentinelSwapInProgress
  475. , idx
  476. ) != idx
  477. );
  478. if (idx == -2)
  479. {
  480. // If the thread was refilling the cache, then try again.
  481. again = true;
  482. }
  483. else if (idx >= 0)
  484. {
  485. // We succeeded in getting an entry from another thread so remove this entry from the
  486. // linked list.
  487. Interlocked.Exchange(ref data->firstFreeTLS[other * UnsafeParallelHashMapData.IntsPerCacheLine], nextPtrs[idx]);
  488. nextPtrs[idx] = -1;
  489. return idx;
  490. }
  491. }
  492. }
  493. ThrowFull();
  494. }
  495. CheckOutOfCapacity(idx, data->keyCapacity);
  496. }
  497. while (Interlocked.CompareExchange(
  498. ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine]
  499. , SentinelSwapInProgress
  500. , idx
  501. ) != idx
  502. );
  503. Interlocked.Exchange(ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine], nextPtrs[idx]);
  504. nextPtrs[idx] = -1;
  505. return idx;
  506. }
  507. internal static unsafe void FreeEntry(UnsafeParallelHashMapData* data, int idx, int threadIndex)
  508. {
  509. int* nextPtrs = (int*)data->next;
  510. int next = -1;
  511. do
  512. {
  513. do
  514. {
  515. next = Volatile.Read(ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine]);
  516. } while (next == SentinelSwapInProgress);
  517. nextPtrs[idx] = next;
  518. }
  519. while (Interlocked.CompareExchange(
  520. ref data->firstFreeTLS[threadIndex * UnsafeParallelHashMapData.IntsPerCacheLine]
  521. , idx
  522. , next
  523. ) != next
  524. );
  525. }
  526. internal static unsafe bool TryAddAtomic(UnsafeParallelHashMapData* data, TKey key, TValue item, int threadIndex)
  527. {
  528. TValue tempItem;
  529. NativeParallelMultiHashMapIterator<TKey> tempIt;
  530. if (TryGetFirstValueAtomic(data, key, out tempItem, out tempIt))
  531. {
  532. return false;
  533. }
  534. // Allocate an entry from the free list
  535. int idx = AllocEntry(data, threadIndex);
  536. // Write the new value to the entry
  537. UnsafeUtility.WriteArrayElement(data->keys, idx, key);
  538. UnsafeUtility.WriteArrayElement(data->values, idx, item);
  539. int bucket = key.GetHashCode() & data->bucketCapacityMask;
  540. // Add the index to the hash-map
  541. int* buckets = (int*)data->buckets;
  542. // Make the bucket's head idx. If the exchange returns something other than -1, then the bucket had
  543. // a non-null head which means we need to do more checks...
  544. if (Interlocked.CompareExchange(ref buckets[bucket], idx, -1) != -1)
  545. {
  546. int* nextPtrs = (int*)data->next;
  547. int next = -1;
  548. do
  549. {
  550. // Link up this entry with the rest of the bucket under the assumption that this key
  551. // doesn't already exist in the bucket. This assumption could be wrong, which will be
  552. // checked later.
  553. next = buckets[bucket];
  554. nextPtrs[idx] = next;
  555. // If the key already exists then we should free the entry we took earlier.
  556. if (TryGetFirstValueAtomic(data, key, out tempItem, out tempIt))
  557. {
  558. // Put back the entry in the free list if someone else added it while trying to add
  559. FreeEntry(data, idx, threadIndex);
  560. return false;
  561. }
  562. }
  563. while (Interlocked.CompareExchange(ref buckets[bucket], idx, next) != next);
  564. }
  565. return true;
  566. }
  567. internal static unsafe void AddAtomicMulti(UnsafeParallelHashMapData* data, TKey key, TValue item, int threadIndex)
  568. {
  569. // Allocate an entry from the free list
  570. int idx = AllocEntry(data, threadIndex);
  571. // Write the new value to the entry
  572. UnsafeUtility.WriteArrayElement(data->keys, idx, key);
  573. UnsafeUtility.WriteArrayElement(data->values, idx, item);
  574. int bucket = key.GetHashCode() & data->bucketCapacityMask;
  575. // Add the index to the hash-map
  576. int* buckets = (int*)data->buckets;
  577. int nextPtr;
  578. int* nextPtrs = (int*)data->next;
  579. do
  580. {
  581. nextPtr = buckets[bucket];
  582. nextPtrs[idx] = nextPtr;
  583. }
  584. while (Interlocked.CompareExchange(ref buckets[bucket], idx, nextPtr) != nextPtr);
  585. }
  586. internal static unsafe bool TryAdd(UnsafeParallelHashMapData* data, TKey key, TValue item, bool isMultiHashMap, AllocatorManager.AllocatorHandle allocation)
  587. {
  588. TValue tempItem;
  589. NativeParallelMultiHashMapIterator<TKey> tempIt;
  590. if (isMultiHashMap || !TryGetFirstValueAtomic(data, key, out tempItem, out tempIt))
  591. {
  592. // Allocate an entry from the free list
  593. int idx;
  594. int* nextPtrs;
  595. if (data->allocatedIndexLength >= data->keyCapacity && data->firstFreeTLS[0] < 0)
  596. {
  597. #if UNITY_2022_2_14F1_OR_NEWER
  598. int maxThreadCount = JobsUtility.ThreadIndexCount;
  599. #else
  600. int maxThreadCount = JobsUtility.MaxJobThreadCount;
  601. #endif
  602. for (int tls = 1; tls < maxThreadCount; ++tls)
  603. {
  604. if (data->firstFreeTLS[tls * UnsafeParallelHashMapData.IntsPerCacheLine] >= 0)
  605. {
  606. idx = data->firstFreeTLS[tls * UnsafeParallelHashMapData.IntsPerCacheLine];
  607. nextPtrs = (int*)data->next;
  608. data->firstFreeTLS[tls * UnsafeParallelHashMapData.IntsPerCacheLine] = nextPtrs[idx];
  609. nextPtrs[idx] = -1;
  610. data->firstFreeTLS[0] = idx;
  611. break;
  612. }
  613. }
  614. if (data->firstFreeTLS[0] < 0)
  615. {
  616. int newCap = UnsafeParallelHashMapData.GrowCapacity(data->keyCapacity);
  617. UnsafeParallelHashMapData.ReallocateHashMap<TKey, TValue>(data, newCap, UnsafeParallelHashMapData.GetBucketSize(newCap), allocation);
  618. }
  619. }
  620. idx = data->firstFreeTLS[0];
  621. if (idx >= 0)
  622. {
  623. data->firstFreeTLS[0] = ((int*)data->next)[idx];
  624. }
  625. else
  626. {
  627. idx = data->allocatedIndexLength++;
  628. }
  629. CheckIndexOutOfBounds(data, idx);
  630. // Write the new value to the entry
  631. UnsafeUtility.WriteArrayElement(data->keys, idx, key);
  632. UnsafeUtility.WriteArrayElement(data->values, idx, item);
  633. int bucket = key.GetHashCode() & data->bucketCapacityMask;
  634. // Add the index to the hash-map
  635. int* buckets = (int*)data->buckets;
  636. nextPtrs = (int*)data->next;
  637. nextPtrs[idx] = buckets[bucket];
  638. buckets[bucket] = idx;
  639. return true;
  640. }
  641. return false;
  642. }
  643. internal static unsafe int Remove(UnsafeParallelHashMapData* data, TKey key, bool isMultiHashMap)
  644. {
  645. if (data->keyCapacity == 0)
  646. {
  647. return 0;
  648. }
  649. var removed = 0;
  650. // First find the slot based on the hash
  651. var buckets = (int*)data->buckets;
  652. var nextPtrs = (int*)data->next;
  653. var bucket = key.GetHashCode() & data->bucketCapacityMask;
  654. var prevEntry = -1;
  655. var entryIdx = buckets[bucket];
  656. while (entryIdx >= 0 && entryIdx < data->keyCapacity)
  657. {
  658. if (UnsafeUtility.ReadArrayElement<TKey>(data->keys, entryIdx).Equals(key))
  659. {
  660. ++removed;
  661. // Found matching element, remove it
  662. if (prevEntry < 0)
  663. {
  664. buckets[bucket] = nextPtrs[entryIdx];
  665. }
  666. else
  667. {
  668. nextPtrs[prevEntry] = nextPtrs[entryIdx];
  669. }
  670. // And free the index
  671. int nextIdx = nextPtrs[entryIdx];
  672. nextPtrs[entryIdx] = data->firstFreeTLS[0];
  673. data->firstFreeTLS[0] = entryIdx;
  674. entryIdx = nextIdx;
  675. // Can only be one hit in regular hashmaps, so return
  676. if (!isMultiHashMap)
  677. {
  678. break;
  679. }
  680. }
  681. else
  682. {
  683. prevEntry = entryIdx;
  684. entryIdx = nextPtrs[entryIdx];
  685. }
  686. }
  687. return removed;
  688. }
  689. internal static unsafe void Remove(UnsafeParallelHashMapData* data, NativeParallelMultiHashMapIterator<TKey> it)
  690. {
  691. // First find the slot based on the hash
  692. int* buckets = (int*)data->buckets;
  693. int* nextPtrs = (int*)data->next;
  694. int bucket = it.key.GetHashCode() & data->bucketCapacityMask;
  695. int entryIdx = buckets[bucket];
  696. if (entryIdx == it.EntryIndex)
  697. {
  698. buckets[bucket] = nextPtrs[entryIdx];
  699. }
  700. else
  701. {
  702. while (entryIdx >= 0 && nextPtrs[entryIdx] != it.EntryIndex)
  703. {
  704. entryIdx = nextPtrs[entryIdx];
  705. }
  706. if (entryIdx < 0)
  707. {
  708. ThrowInvalidIterator();
  709. }
  710. nextPtrs[entryIdx] = nextPtrs[it.EntryIndex];
  711. }
  712. // And free the index
  713. nextPtrs[it.EntryIndex] = data->firstFreeTLS[0];
  714. data->firstFreeTLS[0] = it.EntryIndex;
  715. }
  716. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
  717. internal static unsafe void RemoveKeyValue<TValueEQ>(UnsafeParallelHashMapData* data, TKey key, TValueEQ value)
  718. where TValueEQ : unmanaged, IEquatable<TValueEQ>
  719. {
  720. if (data->keyCapacity == 0)
  721. {
  722. return;
  723. }
  724. var buckets = (int*)data->buckets;
  725. var keyCapacity = (uint)data->keyCapacity;
  726. var prevNextPtr = buckets + (key.GetHashCode() & data->bucketCapacityMask);
  727. var entryIdx = *prevNextPtr;
  728. if ((uint)entryIdx >= keyCapacity)
  729. {
  730. return;
  731. }
  732. var nextPtrs = (int*)data->next;
  733. var keys = data->keys;
  734. var values = data->values;
  735. var firstFreeTLS = data->firstFreeTLS;
  736. do
  737. {
  738. if (UnsafeUtility.ReadArrayElement<TKey>(keys, entryIdx).Equals(key)
  739. && UnsafeUtility.ReadArrayElement<TValueEQ>(values, entryIdx).Equals(value))
  740. {
  741. int nextIdx = nextPtrs[entryIdx];
  742. nextPtrs[entryIdx] = firstFreeTLS[0];
  743. firstFreeTLS[0] = entryIdx;
  744. *prevNextPtr = entryIdx = nextIdx;
  745. }
  746. else
  747. {
  748. prevNextPtr = nextPtrs + entryIdx;
  749. entryIdx = *prevNextPtr;
  750. }
  751. }
  752. while ((uint)entryIdx < keyCapacity);
  753. }
  754. internal static unsafe bool TryGetFirstValueAtomic(UnsafeParallelHashMapData* data, TKey key, out TValue item, out NativeParallelMultiHashMapIterator<TKey> it)
  755. {
  756. it.key = key;
  757. if (data->allocatedIndexLength <= 0)
  758. {
  759. it.EntryIndex = it.NextEntryIndex = -1;
  760. item = default;
  761. return false;
  762. }
  763. // First find the slot based on the hash
  764. int* buckets = (int*)data->buckets;
  765. int bucket = key.GetHashCode() & data->bucketCapacityMask;
  766. it.EntryIndex = it.NextEntryIndex = buckets[bucket];
  767. return TryGetNextValueAtomic(data, out item, ref it);
  768. }
  769. internal static unsafe bool TryGetNextValueAtomic(UnsafeParallelHashMapData* data, out TValue item, ref NativeParallelMultiHashMapIterator<TKey> it)
  770. {
  771. int entryIdx = it.NextEntryIndex;
  772. it.NextEntryIndex = -1;
  773. it.EntryIndex = -1;
  774. item = default;
  775. if (entryIdx < 0 || entryIdx >= data->keyCapacity)
  776. {
  777. return false;
  778. }
  779. int* nextPtrs = (int*)data->next;
  780. while (!UnsafeUtility.ReadArrayElement<TKey>(data->keys, entryIdx).Equals(it.key))
  781. {
  782. entryIdx = nextPtrs[entryIdx];
  783. if (entryIdx < 0 || entryIdx >= data->keyCapacity)
  784. {
  785. return false;
  786. }
  787. }
  788. it.NextEntryIndex = nextPtrs[entryIdx];
  789. it.EntryIndex = entryIdx;
  790. // Read the value
  791. item = UnsafeUtility.ReadArrayElement<TValue>(data->values, entryIdx);
  792. return true;
  793. }
  794. internal static unsafe bool SetValue(UnsafeParallelHashMapData* data, ref NativeParallelMultiHashMapIterator<TKey> it, ref TValue item)
  795. {
  796. int entryIdx = it.EntryIndex;
  797. if (entryIdx < 0 || entryIdx >= data->keyCapacity)
  798. {
  799. return false;
  800. }
  801. UnsafeUtility.WriteArrayElement(data->values, entryIdx, item);
  802. return true;
  803. }
  804. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  805. static void CheckOutOfCapacity(int idx, int keyCapacity)
  806. {
  807. if (idx >= keyCapacity)
  808. {
  809. throw new InvalidOperationException(string.Format("nextPtr idx {0} beyond capacity {1}", idx, keyCapacity));
  810. }
  811. }
  812. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  813. static unsafe void CheckIndexOutOfBounds(UnsafeParallelHashMapData* data, int idx)
  814. {
  815. if (idx < 0 || idx >= data->keyCapacity)
  816. throw new InvalidOperationException("Internal HashMap error");
  817. }
  818. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  819. static void ThrowFull()
  820. {
  821. throw new InvalidOperationException("HashMap is full");
  822. }
  823. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  824. static void ThrowInvalidIterator()
  825. {
  826. throw new InvalidOperationException("Invalid iterator passed to HashMap remove");
  827. }
  828. }
  829. /// <summary>
  830. /// A key-value pair.
  831. /// </summary>
  832. /// <remarks>Used for enumerators.</remarks>
  833. /// <typeparam name="TKey">The type of the keys.</typeparam>
  834. /// <typeparam name="TValue">The type of the values.</typeparam>
  835. [DebuggerDisplay("Key = {Key}, Value = {Value}")]
  836. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] {typeof(int), typeof(int)})]
  837. public unsafe struct KeyValue<TKey, TValue>
  838. where TKey : unmanaged, IEquatable<TKey>
  839. where TValue : unmanaged
  840. {
  841. internal UnsafeParallelHashMapData* m_Buffer;
  842. internal int m_Index;
  843. internal int m_Next;
  844. /// <summary>
  845. /// An invalid KeyValue.
  846. /// </summary>
  847. /// <value>In a hash map enumerator's initial state, its <see cref="UnsafeParallelHashMap{TKey,TValue}.Enumerator.Current"/> value is Null.</value>
  848. public static KeyValue<TKey, TValue> Null => new KeyValue<TKey, TValue>{m_Index = -1};
  849. /// <summary>
  850. /// The key.
  851. /// </summary>
  852. /// <value>The key. If this KeyValue is Null, returns the default of TKey.</value>
  853. public TKey Key
  854. {
  855. get
  856. {
  857. if (m_Index != -1)
  858. {
  859. return UnsafeUtility.ReadArrayElement<TKey>(m_Buffer->keys, m_Index);
  860. }
  861. return default;
  862. }
  863. }
  864. /// <summary>
  865. /// Value of key/value pair.
  866. /// </summary>
  867. public ref TValue Value
  868. {
  869. get
  870. {
  871. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  872. if (m_Index == -1)
  873. throw new ArgumentException("must be valid");
  874. #endif
  875. return ref UnsafeUtility.AsRef<TValue>(m_Buffer->values + UnsafeUtility.SizeOf<TValue>() * m_Index);
  876. }
  877. }
  878. /// <summary>
  879. /// Gets the key and the value.
  880. /// </summary>
  881. /// <param name="key">Outputs the key. If this KeyValue is Null, outputs the default of TKey.</param>
  882. /// <param name="value">Outputs the value. If this KeyValue is Null, outputs the default of TValue.</param>
  883. /// <returns>True if the key-value pair is valid.</returns>
  884. public bool GetKeyValue(out TKey key, out TValue value)
  885. {
  886. if (m_Index != -1)
  887. {
  888. key = UnsafeUtility.ReadArrayElement<TKey>(m_Buffer->keys, m_Index);
  889. value = UnsafeUtility.ReadArrayElement<TValue>(m_Buffer->values, m_Index);
  890. return true;
  891. }
  892. key = default;
  893. value = default;
  894. return false;
  895. }
  896. }
  897. internal unsafe struct UnsafeParallelHashMapDataEnumerator
  898. {
  899. [NativeDisableUnsafePtrRestriction]
  900. internal UnsafeParallelHashMapData* m_Buffer;
  901. internal int m_Index;
  902. internal int m_BucketIndex;
  903. internal int m_NextIndex;
  904. internal unsafe UnsafeParallelHashMapDataEnumerator(UnsafeParallelHashMapData* data)
  905. {
  906. m_Buffer = data;
  907. m_Index = -1;
  908. m_BucketIndex = 0;
  909. m_NextIndex = -1;
  910. }
  911. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  912. internal bool MoveNext()
  913. {
  914. return UnsafeParallelHashMapData.MoveNext(m_Buffer, ref m_BucketIndex, ref m_NextIndex, out m_Index);
  915. }
  916. internal void Reset()
  917. {
  918. m_Index = -1;
  919. m_BucketIndex = 0;
  920. m_NextIndex = -1;
  921. }
  922. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  923. internal KeyValue<TKey, TValue> GetCurrent<TKey, TValue>()
  924. where TKey : unmanaged, IEquatable<TKey>
  925. where TValue : unmanaged
  926. {
  927. return new KeyValue<TKey, TValue> { m_Buffer = m_Buffer, m_Index = m_Index };
  928. }
  929. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  930. internal TKey GetCurrentKey<TKey>()
  931. where TKey : unmanaged, IEquatable<TKey>
  932. {
  933. if (m_Index != -1)
  934. {
  935. return UnsafeUtility.ReadArrayElement<TKey>(m_Buffer->keys, m_Index);
  936. }
  937. return default;
  938. }
  939. }
  940. /// <summary>
  941. /// An unordered, expandable associative array.
  942. /// </summary>
  943. /// <typeparam name="TKey">The type of the keys.</typeparam>
  944. /// <typeparam name="TValue">The type of the values.</typeparam>
  945. [StructLayout(LayoutKind.Sequential)]
  946. [DebuggerDisplay("Count = {Count()}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")]
  947. [DebuggerTypeProxy(typeof(UnsafeParallelHashMapDebuggerTypeProxy<,>))]
  948. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  949. public unsafe struct UnsafeParallelHashMap<TKey, TValue>
  950. : INativeDisposable
  951. , IEnumerable<KeyValue<TKey, TValue>> // Used by collection initializers.
  952. where TKey : unmanaged, IEquatable<TKey>
  953. where TValue : unmanaged
  954. {
  955. [NativeDisableUnsafePtrRestriction]
  956. internal UnsafeParallelHashMapData* m_Buffer;
  957. internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
  958. /// <summary>
  959. /// Initializes and returns an instance of UnsafeParallelHashMap.
  960. /// </summary>
  961. /// <param name="capacity">The number of key-value pairs that should fit in the initial allocation.</param>
  962. /// <param name="allocator">The allocator to use.</param>
  963. public UnsafeParallelHashMap(int capacity, AllocatorManager.AllocatorHandle allocator)
  964. {
  965. m_AllocatorLabel = allocator;
  966. // Bucket size if bigger to reduce collisions
  967. UnsafeParallelHashMapData.AllocateHashMap<TKey, TValue>(capacity, capacity * 2, allocator, out m_Buffer);
  968. Clear();
  969. }
  970. /// <summary>
  971. /// Whether this hash map has been allocated (and not yet deallocated).
  972. /// </summary>
  973. /// <value>True if this hash map has been allocated (and not yet deallocated).</value>
  974. public readonly bool IsCreated
  975. {
  976. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  977. get => m_Buffer != null;
  978. }
  979. /// <summary>
  980. /// Whether this hash map is empty.
  981. /// </summary>
  982. /// <value>True if this hash map is empty or the hash map has not been constructed.</value>
  983. public readonly bool IsEmpty
  984. {
  985. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  986. get => !IsCreated || UnsafeParallelHashMapData.IsEmpty(m_Buffer);
  987. }
  988. /// <summary>
  989. /// The current number of key-value pairs in this hash map.
  990. /// </summary>
  991. /// <returns>The current number of key-value pairs in this hash map.</returns>
  992. public readonly int Count() => UnsafeParallelHashMapData.GetCount(m_Buffer);
  993. /// <summary>
  994. /// The number of key-value pairs that fit in the current allocation.
  995. /// </summary>
  996. /// <value>The number of key-value pairs that fit in the current allocation.</value>
  997. /// <param name="value">A new capacity. Must be larger than the current capacity.</param>
  998. /// <exception cref="InvalidOperationException">Thrown if `value` is less than the current capacity.</exception>
  999. public int Capacity
  1000. {
  1001. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1002. readonly get
  1003. {
  1004. UnsafeParallelHashMapData* data = m_Buffer;
  1005. return data->keyCapacity;
  1006. }
  1007. set
  1008. {
  1009. UnsafeParallelHashMapData* data = m_Buffer;
  1010. UnsafeParallelHashMapData.ReallocateHashMap<TKey, TValue>(data, value, UnsafeParallelHashMapData.GetBucketSize(value), m_AllocatorLabel);
  1011. }
  1012. }
  1013. /// <summary>
  1014. /// Removes all key-value pairs.
  1015. /// </summary>
  1016. /// <remarks>Does not change the capacity.</remarks>
  1017. public void Clear()
  1018. {
  1019. UnsafeParallelHashMapBase<TKey, TValue>.Clear(m_Buffer);
  1020. }
  1021. /// <summary>
  1022. /// Adds a new key-value pair.
  1023. /// </summary>
  1024. /// <remarks>If the key is already present, this method returns false without modifying the hash map.</remarks>
  1025. /// <param name="key">The key to add.</param>
  1026. /// <param name="item">The value to add.</param>
  1027. /// <returns>True if the key-value pair was added.</returns>
  1028. public bool TryAdd(TKey key, TValue item)
  1029. {
  1030. return UnsafeParallelHashMapBase<TKey, TValue>.TryAdd(m_Buffer, key, item, false, m_AllocatorLabel);
  1031. }
  1032. /// <summary>
  1033. /// Adds a new key-value pair.
  1034. /// </summary>
  1035. /// <remarks>If the key is already present, this method throws without modifying the hash map.</remarks>
  1036. /// <param name="key">The key to add.</param>
  1037. /// <param name="item">The value to add.</param>
  1038. /// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
  1039. public void Add(TKey key, TValue item)
  1040. {
  1041. UnsafeParallelHashMapBase<TKey, TValue>.TryAdd(m_Buffer, key, item, false, m_AllocatorLabel);
  1042. }
  1043. /// <summary>
  1044. /// Removes a key-value pair.
  1045. /// </summary>
  1046. /// <param name="key">The key to remove.</param>
  1047. /// <returns>True if a key-value pair was removed.</returns>
  1048. public bool Remove(TKey key)
  1049. {
  1050. return UnsafeParallelHashMapBase<TKey, TValue>.Remove(m_Buffer, key, false) != 0;
  1051. }
  1052. /// <summary>
  1053. /// Returns the value associated with a key.
  1054. /// </summary>
  1055. /// <param name="key">The key to look up.</param>
  1056. /// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
  1057. /// <returns>True if the key was present.</returns>
  1058. public bool TryGetValue(TKey key, out TValue item)
  1059. {
  1060. NativeParallelMultiHashMapIterator<TKey> tempIt;
  1061. return UnsafeParallelHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out item, out tempIt);
  1062. }
  1063. /// <summary>
  1064. /// Returns true if a given key is present in this hash map.
  1065. /// </summary>
  1066. /// <param name="key">The key to look up.</param>
  1067. /// <returns>True if the key was present.</returns>
  1068. public bool ContainsKey(TKey key)
  1069. {
  1070. return UnsafeParallelHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out var tempValue, out var tempIt);
  1071. }
  1072. /// <summary>
  1073. /// Gets and sets values by key.
  1074. /// </summary>
  1075. /// <remarks>Getting a key that is not present will throw. Setting a key that is not already present will add the key.</remarks>
  1076. /// <param name="key">The key to look up.</param>
  1077. /// <value>The value associated with the key.</value>
  1078. /// <exception cref="ArgumentException">For getting, thrown if the key was not present.</exception>
  1079. public TValue this[TKey key]
  1080. {
  1081. get
  1082. {
  1083. TValue res;
  1084. TryGetValue(key, out res);
  1085. return res;
  1086. }
  1087. set
  1088. {
  1089. if (UnsafeParallelHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out var item, out var iterator))
  1090. {
  1091. UnsafeParallelHashMapBase<TKey, TValue>.SetValue(m_Buffer, ref iterator, ref value);
  1092. }
  1093. else
  1094. {
  1095. UnsafeParallelHashMapBase<TKey, TValue>.TryAdd(m_Buffer, key, value, false, m_AllocatorLabel);
  1096. }
  1097. }
  1098. }
  1099. /// <summary>
  1100. /// Releases all resources (memory).
  1101. /// </summary>
  1102. public void Dispose()
  1103. {
  1104. if (!IsCreated)
  1105. {
  1106. return;
  1107. }
  1108. UnsafeParallelHashMapData.DeallocateHashMap(m_Buffer, m_AllocatorLabel);
  1109. m_Buffer = null;
  1110. }
  1111. /// <summary>
  1112. /// Creates and schedules a job that will dispose this hash map.
  1113. /// </summary>
  1114. /// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
  1115. /// <returns>The handle of a new job that will dispose this hash map.</returns>
  1116. public JobHandle Dispose(JobHandle inputDeps)
  1117. {
  1118. if (!IsCreated)
  1119. {
  1120. return inputDeps;
  1121. }
  1122. var jobHandle = new UnsafeParallelHashMapDisposeJob { Data = m_Buffer, Allocator = m_AllocatorLabel }.Schedule(inputDeps);
  1123. m_Buffer = null;
  1124. return jobHandle;
  1125. }
  1126. /// <summary>
  1127. /// Returns an array with a copy of all this hash map's keys (in no particular order).
  1128. /// </summary>
  1129. /// <param name="allocator">The allocator to use.</param>
  1130. /// <returns>An array with a copy of all this hash map's keys (in no particular order).</returns>
  1131. public NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator)
  1132. {
  1133. var result = CollectionHelper.CreateNativeArray<TKey>(UnsafeParallelHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
  1134. UnsafeParallelHashMapData.GetKeyArray(m_Buffer, result);
  1135. return result;
  1136. }
  1137. /// <summary>
  1138. /// Returns an array with a copy of all this hash map's values (in no particular order).
  1139. /// </summary>
  1140. /// <param name="allocator">The allocator to use.</param>
  1141. /// <returns>An array with a copy of all this hash map's values (in no particular order).</returns>
  1142. public NativeArray<TValue> GetValueArray(AllocatorManager.AllocatorHandle allocator)
  1143. {
  1144. var result = CollectionHelper.CreateNativeArray<TValue>(UnsafeParallelHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
  1145. UnsafeParallelHashMapData.GetValueArray(m_Buffer, result);
  1146. return result;
  1147. }
  1148. /// <summary>
  1149. /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values.
  1150. /// </summary>
  1151. /// <remarks>The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`.</remarks>
  1152. /// <param name="allocator">The allocator to use.</param>
  1153. /// <returns>A NativeKeyValueArrays with a copy of all this hash map's keys and values.</returns>
  1154. public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator)
  1155. {
  1156. var result = new NativeKeyValueArrays<TKey, TValue>(UnsafeParallelHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
  1157. UnsafeParallelHashMapData.GetKeyValueArrays(m_Buffer, result);
  1158. return result;
  1159. }
  1160. /// <summary>
  1161. /// Returns a parallel writer for this hash map.
  1162. /// </summary>
  1163. /// <returns>A parallel writer for this hash map.</returns>
  1164. public ParallelWriter AsParallelWriter()
  1165. {
  1166. ParallelWriter writer;
  1167. writer.m_ThreadIndex = 0;
  1168. writer.m_Buffer = m_Buffer;
  1169. return writer;
  1170. }
  1171. /// <summary>
  1172. /// Returns a readonly version of this UnsafeParallelHashMap instance.
  1173. /// </summary>
  1174. /// <remarks>ReadOnly containers point to the same underlying data as the UnsafeParallelHashMap it is made from.</remarks>
  1175. /// <returns>ReadOnly instance for this.</returns>
  1176. public ReadOnly AsReadOnly()
  1177. {
  1178. return new ReadOnly(this);
  1179. }
  1180. /// <summary>
  1181. /// A read-only alias for the value of a UnsafeParallelHashMap. Does not have its own allocated storage.
  1182. /// </summary>
  1183. [DebuggerDisplay("Count = {m_HashMapData.Count()}, Capacity = {m_HashMapData.Capacity}, IsCreated = {m_HashMapData.IsCreated}, IsEmpty = {IsEmpty}")]
  1184. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
  1185. public struct ReadOnly
  1186. : IEnumerable<KeyValue<TKey, TValue>>
  1187. {
  1188. internal UnsafeParallelHashMap<TKey, TValue> m_HashMapData;
  1189. internal ReadOnly(UnsafeParallelHashMap<TKey, TValue> hashMapData)
  1190. {
  1191. m_HashMapData = hashMapData;
  1192. }
  1193. /// <summary>
  1194. /// Whether this hash map has been allocated (and not yet deallocated).
  1195. /// </summary>
  1196. /// <value>True if this hash map has been allocated (and not yet deallocated).</value>
  1197. public readonly bool IsCreated
  1198. {
  1199. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1200. get => m_HashMapData.IsCreated;
  1201. }
  1202. /// <summary>
  1203. /// Whether this hash map is empty.
  1204. /// </summary>
  1205. /// <value>True if this hash map is empty or if the map has not been constructed.</value>
  1206. public readonly bool IsEmpty
  1207. {
  1208. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1209. get
  1210. {
  1211. if (!IsCreated)
  1212. {
  1213. return true;
  1214. }
  1215. return m_HashMapData.IsEmpty;
  1216. }
  1217. }
  1218. /// <summary>
  1219. /// The current number of key-value pairs in this hash map.
  1220. /// </summary>
  1221. /// <returns>The current number of key-value pairs in this hash map.</returns>
  1222. public readonly int Count()
  1223. {
  1224. return m_HashMapData.Count();
  1225. }
  1226. /// <summary>
  1227. /// The number of key-value pairs that fit in the current allocation.
  1228. /// </summary>
  1229. /// <value>The number of key-value pairs that fit in the current allocation.</value>
  1230. public readonly int Capacity
  1231. {
  1232. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1233. get
  1234. {
  1235. return m_HashMapData.Capacity;
  1236. }
  1237. }
  1238. /// <summary>
  1239. /// Returns the value associated with a key.
  1240. /// </summary>
  1241. /// <param name="key">The key to look up.</param>
  1242. /// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
  1243. /// <returns>True if the key was present.</returns>
  1244. public readonly bool TryGetValue(TKey key, out TValue item)
  1245. {
  1246. return m_HashMapData.TryGetValue(key, out item);
  1247. }
  1248. /// <summary>
  1249. /// Returns true if a given key is present in this hash map.
  1250. /// </summary>
  1251. /// <param name="key">The key to look up.</param>
  1252. /// <returns>True if the key was present.</returns>
  1253. public readonly bool ContainsKey(TKey key)
  1254. {
  1255. return m_HashMapData.ContainsKey(key);
  1256. }
  1257. /// <summary>
  1258. /// Gets values by key.
  1259. /// </summary>
  1260. /// <remarks>Getting a key that is not present will throw.</remarks>
  1261. /// <param name="key">The key to look up.</param>
  1262. /// <value>The value associated with the key.</value>
  1263. /// <exception cref="ArgumentException">For getting, thrown if the key was not present.</exception>
  1264. public readonly TValue this[TKey key]
  1265. {
  1266. get
  1267. {
  1268. TValue res;
  1269. if (m_HashMapData.TryGetValue(key, out res))
  1270. {
  1271. return res;
  1272. }
  1273. ThrowKeyNotPresent(key);
  1274. return default;
  1275. }
  1276. }
  1277. /// <summary>
  1278. /// Returns an array with a copy of all this hash map's keys (in no particular order).
  1279. /// </summary>
  1280. /// <param name="allocator">The allocator to use.</param>
  1281. /// <returns>An array with a copy of all this hash map's keys (in no particular order).</returns>
  1282. public readonly NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator)
  1283. {
  1284. return m_HashMapData.GetKeyArray(allocator);
  1285. }
  1286. /// <summary>
  1287. /// Returns an array with a copy of all this hash map's values (in no particular order).
  1288. /// </summary>
  1289. /// <param name="allocator">The allocator to use.</param>
  1290. /// <returns>An array with a copy of all this hash map's values (in no particular order).</returns>
  1291. public readonly NativeArray<TValue> GetValueArray(AllocatorManager.AllocatorHandle allocator)
  1292. {
  1293. return m_HashMapData.GetValueArray(allocator);
  1294. }
  1295. /// <summary>
  1296. /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values.
  1297. /// </summary>
  1298. /// <remarks>The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`.</remarks>
  1299. /// <param name="allocator">The allocator to use.</param>
  1300. /// <returns>A NativeKeyValueArrays with a copy of all this hash map's keys and values.</returns>
  1301. public readonly NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator)
  1302. {
  1303. return m_HashMapData.GetKeyValueArrays(allocator);
  1304. }
  1305. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  1306. readonly void ThrowKeyNotPresent(TKey key)
  1307. {
  1308. throw new ArgumentException($"Key: {key} is not present in the NativeParallelHashMap.");
  1309. }
  1310. /// <summary>
  1311. /// Returns an enumerator over the key-value pairs of this hash map.
  1312. /// </summary>
  1313. /// <returns>An enumerator over the key-value pairs of this hash map.</returns>
  1314. public readonly Enumerator GetEnumerator()
  1315. {
  1316. return new Enumerator
  1317. {
  1318. m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_HashMapData.m_Buffer),
  1319. };
  1320. }
  1321. /// <summary>
  1322. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  1323. /// </summary>
  1324. /// <returns>Throws NotImplementedException.</returns>
  1325. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  1326. IEnumerator<KeyValue<TKey, TValue>> IEnumerable<KeyValue<TKey, TValue>>.GetEnumerator()
  1327. {
  1328. throw new NotImplementedException();
  1329. }
  1330. /// <summary>
  1331. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  1332. /// </summary>
  1333. /// <returns>Throws NotImplementedException.</returns>
  1334. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  1335. IEnumerator IEnumerable.GetEnumerator()
  1336. {
  1337. throw new NotImplementedException();
  1338. }
  1339. }
  1340. /// <summary>
  1341. /// A parallel writer for a NativeParallelHashMap.
  1342. /// </summary>
  1343. /// <remarks>
  1344. /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a NativeParallelHashMap.
  1345. /// </remarks>
  1346. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  1347. public unsafe struct ParallelWriter
  1348. {
  1349. [NativeDisableUnsafePtrRestriction]
  1350. internal UnsafeParallelHashMapData* m_Buffer;
  1351. [NativeSetThreadIndex]
  1352. internal int m_ThreadIndex;
  1353. /// <summary>
  1354. /// Returns the index of the current thread.
  1355. /// </summary>
  1356. /// <remarks>In a job, each thread gets its own copy of the ParallelWriter struct, and the job system assigns
  1357. /// each copy the index of its thread.</remarks>
  1358. /// <value>The index of the current thread.</value>
  1359. public int ThreadIndex => m_ThreadIndex;
  1360. /// <summary>
  1361. /// The number of key-value pairs that fit in the current allocation.
  1362. /// </summary>
  1363. /// <value>The number of key-value pairs that fit in the current allocation.</value>
  1364. public readonly int Capacity
  1365. {
  1366. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1367. get
  1368. {
  1369. UnsafeParallelHashMapData* data = m_Buffer;
  1370. return data->keyCapacity;
  1371. }
  1372. }
  1373. /// <summary>
  1374. /// Adds a new key-value pair.
  1375. /// </summary>
  1376. /// <remarks>If the key is already present, this method returns false without modifying the hash map.</remarks>
  1377. /// <param name="key">The key to add.</param>
  1378. /// <param name="item">The value to add.</param>
  1379. /// <returns>True if the key-value pair was added.</returns>
  1380. public bool TryAdd(TKey key, TValue item)
  1381. {
  1382. Assert.IsTrue(m_ThreadIndex >= 0);
  1383. return UnsafeParallelHashMapBase<TKey, TValue>.TryAddAtomic(m_Buffer, key, item, m_ThreadIndex);
  1384. }
  1385. /// <summary>
  1386. /// Adds a new key-value pair.
  1387. /// </summary>
  1388. /// <remarks>If the key is already present, this method returns false without modifying the hash map.</remarks>
  1389. /// <param name="key">The key to add.</param>
  1390. /// <param name="item">The value to add.</param>
  1391. /// <param name="threadIndexOverride">The thread index which must be set by a field from a job struct with the <see cref="NativeSetThreadIndexAttribute"/> attribute.</param>
  1392. /// <returns>True if the key-value pair was added.</returns>
  1393. internal bool TryAdd(TKey key, TValue item, int threadIndexOverride)
  1394. {
  1395. Assert.IsTrue(threadIndexOverride >= 0);
  1396. return UnsafeParallelHashMapBase<TKey, TValue>.TryAddAtomic(m_Buffer, key, item, threadIndexOverride);
  1397. }
  1398. }
  1399. /// <summary>
  1400. /// Returns an enumerator over the key-value pairs of this hash map.
  1401. /// </summary>
  1402. /// <returns>An enumerator over the key-value pairs of this hash map.</returns>
  1403. public Enumerator GetEnumerator()
  1404. {
  1405. return new Enumerator { m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_Buffer) };
  1406. }
  1407. /// <summary>
  1408. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  1409. /// </summary>
  1410. /// <returns>Throws NotImplementedException.</returns>
  1411. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  1412. IEnumerator<KeyValue<TKey, TValue>> IEnumerable<KeyValue<TKey, TValue>>.GetEnumerator()
  1413. {
  1414. throw new NotImplementedException();
  1415. }
  1416. /// <summary>
  1417. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  1418. /// </summary>
  1419. /// <returns>Throws NotImplementedException.</returns>
  1420. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  1421. IEnumerator IEnumerable.GetEnumerator()
  1422. {
  1423. throw new NotImplementedException();
  1424. }
  1425. /// <summary>
  1426. /// An enumerator over the key-value pairs of a hash map.
  1427. /// </summary>
  1428. /// <remarks>
  1429. /// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
  1430. /// From this state, the first <see cref="MoveNext"/> call advances the enumerator to the first key-value pair.
  1431. /// </remarks>
  1432. public struct Enumerator : IEnumerator<KeyValue<TKey, TValue>>
  1433. {
  1434. internal UnsafeParallelHashMapDataEnumerator m_Enumerator;
  1435. /// <summary>
  1436. /// Does nothing.
  1437. /// </summary>
  1438. public void Dispose() { }
  1439. /// <summary>
  1440. /// Advances the enumerator to the next key-value pair.
  1441. /// </summary>
  1442. /// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
  1443. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1444. public bool MoveNext() => m_Enumerator.MoveNext();
  1445. /// <summary>
  1446. /// Resets the enumerator to its initial state.
  1447. /// </summary>
  1448. public void Reset() => m_Enumerator.Reset();
  1449. /// <summary>
  1450. /// The current key-value pair.
  1451. /// </summary>
  1452. /// <value>The current key-value pair.</value>
  1453. public KeyValue<TKey, TValue> Current
  1454. {
  1455. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1456. get => m_Enumerator.GetCurrent<TKey, TValue>();
  1457. }
  1458. object IEnumerator.Current => Current;
  1459. }
  1460. }
  1461. [BurstCompile]
  1462. internal unsafe struct UnsafeParallelHashMapDisposeJob : IJob
  1463. {
  1464. [NativeDisableUnsafePtrRestriction]
  1465. public UnsafeParallelHashMapData* Data;
  1466. public AllocatorManager.AllocatorHandle Allocator;
  1467. public void Execute()
  1468. {
  1469. UnsafeParallelHashMapData.DeallocateHashMap(Data, Allocator);
  1470. }
  1471. }
  1472. sealed internal class UnsafeParallelHashMapDebuggerTypeProxy<TKey, TValue>
  1473. where TKey : unmanaged, IEquatable<TKey>
  1474. where TValue : unmanaged
  1475. {
  1476. UnsafeParallelHashMap<TKey, TValue> m_Target;
  1477. public UnsafeParallelHashMapDebuggerTypeProxy(UnsafeParallelHashMap<TKey, TValue> target)
  1478. {
  1479. m_Target = target;
  1480. }
  1481. public List<Pair<TKey, TValue>> Items
  1482. {
  1483. get
  1484. {
  1485. var result = new List<Pair<TKey, TValue>>();
  1486. using (var kva = m_Target.GetKeyValueArrays(Allocator.Temp))
  1487. {
  1488. for (var i = 0; i < kva.Length; ++i)
  1489. {
  1490. result.Add(new Pair<TKey, TValue>(kva.Keys[i], kva.Values[i]));
  1491. }
  1492. }
  1493. return result;
  1494. }
  1495. }
  1496. }
  1497. /// <summary>
  1498. /// For internal use only.
  1499. /// </summary>
  1500. public unsafe struct UntypedUnsafeParallelHashMap
  1501. {
  1502. #pragma warning disable 169
  1503. [NativeDisableUnsafePtrRestriction]
  1504. UnsafeParallelHashMapData* m_Buffer;
  1505. AllocatorManager.AllocatorHandle m_AllocatorLabel;
  1506. #pragma warning restore 169
  1507. }
  1508. }