暫無描述
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.

UnsafeMultiHashMap.cs 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Runtime.InteropServices;
  6. using Unity.Jobs;
  7. using UnityEngine.Assertions;
  8. namespace Unity.Collections.LowLevel.Unsafe
  9. {
  10. /// <summary>
  11. /// An unordered, expandable associative array. Each key can have more than one associated value.
  12. /// </summary>
  13. /// <remarks>
  14. /// Unlike a regular UnsafeHashMap, an UnsafeMultiHashMap can store multiple key-value pairs with the same key.
  15. ///
  16. /// The keys are not deduplicated: two key-value pairs with the same key are stored as fully separate key-value pairs.
  17. /// </remarks>
  18. /// <typeparam name="TKey">The type of the keys.</typeparam>
  19. /// <typeparam name="TValue">The type of the values.</typeparam>
  20. [StructLayout(LayoutKind.Sequential)]
  21. [DebuggerTypeProxy(typeof(UnsafeMultiHashMapDebuggerTypeProxy<,>))]
  22. [BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  23. public unsafe struct UnsafeMultiHashMap<TKey, TValue>
  24. : INativeDisposable
  25. , IEnumerable<KeyValue<TKey, TValue>> // Used by collection initializers.
  26. where TKey : struct, IEquatable<TKey>
  27. where TValue : struct
  28. {
  29. [NativeDisableUnsafePtrRestriction]
  30. internal UnsafeHashMapData* m_Buffer;
  31. internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
  32. /// <summary>
  33. /// Initializes and returns an instance of UnsafeMultiHashMap.
  34. /// </summary>
  35. /// <param name="capacity">The number of key-value pairs that should fit in the initial allocation.</param>
  36. /// <param name="allocator">The allocator to use.</param>
  37. public UnsafeMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator)
  38. {
  39. m_AllocatorLabel = allocator;
  40. // Bucket size if bigger to reduce collisions
  41. UnsafeHashMapData.AllocateHashMap<TKey, TValue>(capacity, capacity * 2, allocator, out m_Buffer);
  42. Clear();
  43. }
  44. /// <summary>
  45. /// Whether this hash map is empty.
  46. /// </summary>
  47. /// <value>True if this hash map is empty or the hash map has not been constructed.</value>
  48. public bool IsEmpty => !IsCreated || UnsafeHashMapData.IsEmpty(m_Buffer);
  49. /// <summary>
  50. /// Returns the current number of key-value pairs in this hash map.
  51. /// </summary>
  52. /// <remarks>Key-value pairs with matching keys are counted as separate, individual pairs.</remarks>
  53. /// <returns>The current number of key-value pairs in this hash map.</returns>
  54. public int Count()
  55. {
  56. if (m_Buffer->allocatedIndexLength <= 0)
  57. {
  58. return 0;
  59. }
  60. return UnsafeHashMapData.GetCount(m_Buffer);
  61. }
  62. /// <summary>
  63. /// Returns the number of key-value pairs that fit in the current allocation.
  64. /// </summary>
  65. /// <value>The number of key-value pairs that fit in the current allocation.</value>
  66. /// <param name="value">A new capacity. Must be larger than the current capacity.</param>
  67. /// <exception cref="Exception">Thrown if `value` is less than the current capacity.</exception>
  68. public int Capacity
  69. {
  70. get
  71. {
  72. UnsafeHashMapData* data = m_Buffer;
  73. return data->keyCapacity;
  74. }
  75. set
  76. {
  77. UnsafeHashMapData* data = m_Buffer;
  78. UnsafeHashMapData.ReallocateHashMap<TKey, TValue>(data, value, UnsafeHashMapData.GetBucketSize(value), m_AllocatorLabel);
  79. }
  80. }
  81. /// <summary>
  82. /// Removes all key-value pairs.
  83. /// </summary>
  84. /// <remarks>Does not change the capacity.</remarks>
  85. public void Clear()
  86. {
  87. UnsafeHashMapBase<TKey, TValue>.Clear(m_Buffer);
  88. }
  89. /// <summary>
  90. /// Adds a new key-value pair.
  91. /// </summary>
  92. /// <remarks>
  93. /// If a key-value pair with this key is already present, an additional separate key-value pair is added.
  94. /// </remarks>
  95. /// <param name="key">The key to add.</param>
  96. /// <param name="item">The value to add.</param>
  97. public void Add(TKey key, TValue item)
  98. {
  99. UnsafeHashMapBase<TKey, TValue>.TryAdd(m_Buffer, key, item, true, m_AllocatorLabel);
  100. }
  101. /// <summary>
  102. /// Removes a key and its associated value(s).
  103. /// </summary>
  104. /// <param name="key">The key to remove.</param>
  105. /// <returns>The number of removed key-value pairs. If the key was not present, returns 0.</returns>
  106. public int Remove(TKey key)
  107. {
  108. return UnsafeHashMapBase<TKey, TValue>.Remove(m_Buffer, key, true);
  109. }
  110. /// <summary>
  111. /// Removes all key-value pairs with a particular key and a particular value.
  112. /// </summary>
  113. /// <remarks>Removes all key-value pairs which have a particular key and which *also have* a particular value.
  114. /// In other words: (key *AND* value) rather than (key *OR* value).</remarks>
  115. /// <typeparam name="TValueEQ">The type of the value.</typeparam>
  116. /// <param name="key">The key of the key-value pairs to remove.</param>
  117. /// <param name="value">The value of the key-value pairs to remove.</param>
  118. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  119. public void Remove<TValueEQ>(TKey key, TValueEQ value)
  120. where TValueEQ : struct, IEquatable<TValueEQ>
  121. {
  122. UnsafeHashMapBase<TKey, TValueEQ>.RemoveKeyValue(m_Buffer, key, value);
  123. }
  124. /// <summary>
  125. /// Removes a single key-value pair.
  126. /// </summary>
  127. /// <param name="it">An iterator representing the key-value pair to remove.</param>
  128. /// <exception cref="InvalidOperationException">Thrown if the iterator is invalid.</exception>
  129. public void Remove(NativeMultiHashMapIterator<TKey> it)
  130. {
  131. UnsafeHashMapBase<TKey, TValue>.Remove(m_Buffer, it);
  132. }
  133. /// <summary>
  134. /// Gets an iterator for a key.
  135. /// </summary>
  136. /// <param name="key">The key.</param>
  137. /// <param name="item">Outputs the associated value represented by the iterator.</param>
  138. /// <param name="it">Outputs an iterator.</param>
  139. /// <returns>True if the key was present.</returns>
  140. public bool TryGetFirstValue(TKey key, out TValue item, out NativeMultiHashMapIterator<TKey> it)
  141. {
  142. return UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out item, out it);
  143. }
  144. /// <summary>
  145. /// Advances an iterator to the next value associated with its key.
  146. /// </summary>
  147. /// <param name="item">Outputs the next value.</param>
  148. /// <param name="it">A reference to the iterator to advance.</param>
  149. /// <returns>True if the key was present and had another value.</returns>
  150. public bool TryGetNextValue(out TValue item, ref NativeMultiHashMapIterator<TKey> it)
  151. {
  152. return UnsafeHashMapBase<TKey, TValue>.TryGetNextValueAtomic(m_Buffer, out item, ref it);
  153. }
  154. /// <summary>
  155. /// Returns true if a given key is present in this hash map.
  156. /// </summary>
  157. /// <param name="key">The key to look up.</param>
  158. /// <returns>True if the key was present in this hash map.</returns>
  159. public bool ContainsKey(TKey key)
  160. {
  161. return TryGetFirstValue(key, out var temp0, out var temp1);
  162. }
  163. /// <summary>
  164. /// Returns the number of values associated with a given key.
  165. /// </summary>
  166. /// <param name="key">The key to look up.</param>
  167. /// <returns>The number of values associated with the key. Returns 0 if the key was not present.</returns>
  168. public int CountValuesForKey(TKey key)
  169. {
  170. if (!TryGetFirstValue(key, out var value, out var iterator))
  171. {
  172. return 0;
  173. }
  174. var count = 1;
  175. while (TryGetNextValue(out value, ref iterator))
  176. {
  177. count++;
  178. }
  179. return count;
  180. }
  181. /// <summary>
  182. /// Sets a new value for an existing key-value pair.
  183. /// </summary>
  184. /// <param name="item">The new value.</param>
  185. /// <param name="it">The iterator representing a key-value pair.</param>
  186. /// <returns>True if a value was overwritten.</returns>
  187. public bool SetValue(TValue item, NativeMultiHashMapIterator<TKey> it)
  188. {
  189. return UnsafeHashMapBase<TKey, TValue>.SetValue(m_Buffer, ref it, ref item);
  190. }
  191. /// <summary>
  192. /// Whether this hash map has been allocated (and not yet deallocated).
  193. /// </summary>
  194. /// <value>True if this hash map has been allocated (and not yet deallocated).</value>
  195. public bool IsCreated => m_Buffer != null;
  196. /// <summary>
  197. /// Releases all resources (memory and safety handles).
  198. /// </summary>
  199. public void Dispose()
  200. {
  201. UnsafeHashMapData.DeallocateHashMap(m_Buffer, m_AllocatorLabel);
  202. m_Buffer = null;
  203. }
  204. /// <summary>
  205. /// Creates and schedules a job that will dispose this hash map.
  206. /// </summary>
  207. /// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
  208. /// <returns>The handle of a new job that will dispose this hash map.</returns>
  209. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
  210. public JobHandle Dispose(JobHandle inputDeps)
  211. {
  212. var jobHandle = new UnsafeHashMapDisposeJob { Data = m_Buffer, Allocator = m_AllocatorLabel }.Schedule(inputDeps);
  213. m_Buffer = null;
  214. return jobHandle;
  215. }
  216. /// <summary>
  217. /// Returns an array with a copy of all the keys (in no particular order).
  218. /// </summary>
  219. /// <remarks>A key with *N* values is included *N* times in the array.
  220. ///
  221. /// Use `GetUniqueKeyArray` of <see cref="Unity.Collections.NativeHashMapExtensions"/> instead if you only want one occurrence of each key.</remarks>
  222. /// <param name="allocator">The allocator to use.</param>
  223. /// <returns>An array with a copy of all the keys (in no particular order).</returns>
  224. public NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator)
  225. {
  226. var result = CollectionHelper.CreateNativeArray<TKey>(Count(), allocator, NativeArrayOptions.UninitializedMemory);
  227. UnsafeHashMapData.GetKeyArray(m_Buffer, result);
  228. return result;
  229. }
  230. /// <summary>
  231. /// Returns an array with a copy of all the values (in no particular order).
  232. /// </summary>
  233. /// <remarks>The values are not deduplicated. If you sort the returned array,
  234. /// you can use <see cref="Unity.Collections.NativeHashMapExtensions.Unique{T}"/> to remove duplicate values.</remarks>
  235. /// <param name="allocator">The allocator to use.</param>
  236. /// <returns>An array with a copy of all the values (in no particular order).</returns>
  237. public NativeArray<TValue> GetValueArray(AllocatorManager.AllocatorHandle allocator)
  238. {
  239. var result = CollectionHelper.CreateNativeArray<TValue>(Count(), allocator, NativeArrayOptions.UninitializedMemory);
  240. UnsafeHashMapData.GetValueArray(m_Buffer, result);
  241. return result;
  242. }
  243. /// <summary>
  244. /// Returns a NativeKeyValueArrays with a copy of all the keys and values (in no particular order).
  245. /// </summary>
  246. /// <remarks>A key with *N* values is included *N* times in the array.
  247. /// </remarks>
  248. /// <param name="allocator">The allocator to use.</param>
  249. /// <returns>A NativeKeyValueArrays with a copy of all the keys and values (in no particular order).</returns>
  250. public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator)
  251. {
  252. var result = new NativeKeyValueArrays<TKey, TValue>(Count(), allocator, NativeArrayOptions.UninitializedMemory);
  253. UnsafeHashMapData.GetKeyValueArrays(m_Buffer, result);
  254. return result;
  255. }
  256. /// <summary>
  257. /// Returns an enumerator over the values of an individual key.
  258. /// </summary>
  259. /// <param name="key">The key to get an enumerator for.</param>
  260. /// <returns>An enumerator over the values of a key.</returns>
  261. public Enumerator GetValuesForKey(TKey key)
  262. {
  263. return new Enumerator { hashmap = this, key = key, isFirst = true };
  264. }
  265. /// <summary>
  266. /// An enumerator over the values of an individual key in a multi hash map.
  267. /// </summary>
  268. /// <remarks>
  269. /// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
  270. /// The first <see cref="MoveNext"/> call advances the enumerator to the first value of the key.
  271. /// </remarks>
  272. public struct Enumerator : IEnumerator<TValue>
  273. {
  274. internal UnsafeMultiHashMap<TKey, TValue> hashmap;
  275. internal TKey key;
  276. internal bool isFirst;
  277. TValue value;
  278. NativeMultiHashMapIterator<TKey> iterator;
  279. /// <summary>
  280. /// Does nothing.
  281. /// </summary>
  282. public void Dispose() { }
  283. /// <summary>
  284. /// Advances the enumerator to the next value of the key.
  285. /// </summary>
  286. /// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
  287. public bool MoveNext()
  288. {
  289. //Avoids going beyond the end of the collection.
  290. if (isFirst)
  291. {
  292. isFirst = false;
  293. return hashmap.TryGetFirstValue(key, out value, out iterator);
  294. }
  295. return hashmap.TryGetNextValue(out value, ref iterator);
  296. }
  297. /// <summary>
  298. /// Resets the enumerator to its initial state.
  299. /// </summary>
  300. public void Reset() => isFirst = true;
  301. /// <summary>
  302. /// The current value.
  303. /// </summary>
  304. /// <value>The current value.</value>
  305. public TValue Current => value;
  306. object IEnumerator.Current => Current;
  307. /// <summary>
  308. /// Returns this enumerator.
  309. /// </summary>
  310. /// <returns>This enumerator.</returns>
  311. public Enumerator GetEnumerator() { return this; }
  312. }
  313. /// <summary>
  314. /// Returns a parallel writer for this hash map.
  315. /// </summary>
  316. /// <returns>A parallel writer for this hash map.</returns>
  317. public ParallelWriter AsParallelWriter()
  318. {
  319. ParallelWriter writer;
  320. #if UNITY_DOTSRUNTIME
  321. writer.m_ThreadIndex = -1; // aggressively check that code-gen has patched the ThreadIndex
  322. #else
  323. writer.m_ThreadIndex = 0;
  324. #endif
  325. writer.m_Buffer = m_Buffer;
  326. return writer;
  327. }
  328. /// <summary>
  329. /// A parallel writer for an UnsafeMultiHashMap.
  330. /// </summary>
  331. /// <remarks>
  332. /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a NativeMultiHashMap.
  333. /// </remarks>
  334. [NativeContainerIsAtomicWriteOnly]
  335. [BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
  336. public unsafe struct ParallelWriter
  337. {
  338. [NativeDisableUnsafePtrRestriction]
  339. internal UnsafeHashMapData* m_Buffer;
  340. [NativeSetThreadIndex]
  341. internal int m_ThreadIndex;
  342. /// <summary>
  343. /// Returns the number of key-value pairs that fit in the current allocation.
  344. /// </summary>
  345. /// <value>The number of key-value pairs that fit in the current allocation.</value>
  346. public int Capacity
  347. {
  348. get
  349. {
  350. return m_Buffer->keyCapacity;
  351. }
  352. }
  353. /// <summary>
  354. /// Adds a new key-value pair.
  355. /// </summary>
  356. /// <remarks>
  357. /// If a key-value pair with this key is already present, an additional separate key-value pair is added.
  358. /// </remarks>
  359. /// <param name="key">The key to add.</param>
  360. /// <param name="item">The value to add.</param>
  361. public void Add(TKey key, TValue item)
  362. {
  363. Assert.IsTrue(m_ThreadIndex >= 0);
  364. UnsafeHashMapBase<TKey, TValue>.AddAtomicMulti(m_Buffer, key, item, m_ThreadIndex);
  365. }
  366. }
  367. /// <summary>
  368. /// Returns an enumerator over the key-value pairs of this hash map.
  369. /// </summary>
  370. /// <remarks>A key with *N* values is visited by the enumerator *N* times.</remarks>
  371. /// <returns>An enumerator over the key-value pairs of this hash map.</returns>
  372. public KeyValueEnumerator GetEnumerator()
  373. {
  374. return new KeyValueEnumerator { m_Enumerator = new UnsafeHashMapDataEnumerator(m_Buffer) };
  375. }
  376. /// <summary>
  377. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  378. /// </summary>
  379. /// <returns>Throws NotImplementedException.</returns>
  380. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  381. IEnumerator<KeyValue<TKey, TValue>> IEnumerable<KeyValue<TKey, TValue>>.GetEnumerator()
  382. {
  383. throw new NotImplementedException();
  384. }
  385. /// <summary>
  386. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  387. /// </summary>
  388. /// <returns>Throws NotImplementedException.</returns>
  389. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  390. IEnumerator IEnumerable.GetEnumerator()
  391. {
  392. throw new NotImplementedException();
  393. }
  394. /// <summary>
  395. /// An enumerator over the key-value pairs of a multi hash map.
  396. /// </summary>
  397. /// <remarks>A key with *N* values is visited by the enumerator *N* times.
  398. ///
  399. /// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
  400. /// The first <see cref="MoveNext"/> call advances the enumerator to the first key-value pair.
  401. /// </remarks>
  402. public struct KeyValueEnumerator : IEnumerator<KeyValue<TKey, TValue>>
  403. {
  404. internal UnsafeHashMapDataEnumerator m_Enumerator;
  405. /// <summary>
  406. /// Does nothing.
  407. /// </summary>
  408. public void Dispose() { }
  409. /// <summary>
  410. /// Advances the enumerator to the next key-value pair.
  411. /// </summary>
  412. /// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
  413. public bool MoveNext() => m_Enumerator.MoveNext();
  414. /// <summary>
  415. /// Resets the enumerator to its initial state.
  416. /// </summary>
  417. public void Reset() => m_Enumerator.Reset();
  418. /// <summary>
  419. /// The current key-value pair.
  420. /// </summary>
  421. /// <value>The current key-value pair.</value>
  422. public KeyValue<TKey, TValue> Current => m_Enumerator.GetCurrent<TKey, TValue>();
  423. object IEnumerator.Current => Current;
  424. }
  425. }
  426. internal sealed class UnsafeMultiHashMapDebuggerTypeProxy<TKey, TValue>
  427. where TKey : struct, IEquatable<TKey>, IComparable<TKey>
  428. where TValue : struct
  429. {
  430. #if !NET_DOTS
  431. UnsafeMultiHashMap<TKey, TValue> m_Target;
  432. public UnsafeMultiHashMapDebuggerTypeProxy(UnsafeMultiHashMap<TKey, TValue> target)
  433. {
  434. m_Target = target;
  435. }
  436. public static (NativeArray<TKey>, int) GetUniqueKeyArray(ref UnsafeMultiHashMap<TKey, TValue> hashMap, AllocatorManager.AllocatorHandle allocator)
  437. {
  438. var withDuplicates = hashMap.GetKeyArray(allocator);
  439. withDuplicates.Sort();
  440. int uniques = withDuplicates.Unique();
  441. return (withDuplicates, uniques);
  442. }
  443. public List<ListPair<TKey, List<TValue>>> Items
  444. {
  445. get
  446. {
  447. var result = new List<ListPair<TKey, List<TValue>>>();
  448. var keys = GetUniqueKeyArray(ref m_Target, Allocator.Temp);
  449. using (keys.Item1)
  450. {
  451. for (var k = 0; k < keys.Item2; ++k)
  452. {
  453. var values = new List<TValue>();
  454. if (m_Target.TryGetFirstValue(keys.Item1[k], out var value, out var iterator))
  455. {
  456. do
  457. {
  458. values.Add(value);
  459. }
  460. while (m_Target.TryGetNextValue(out value, ref iterator));
  461. }
  462. result.Add(new ListPair<TKey, List<TValue>>(keys.Item1[k], values));
  463. }
  464. }
  465. return result;
  466. }
  467. }
  468. #endif
  469. }
  470. }