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

NativeHashSet.cs 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Runtime.InteropServices;
  6. using Unity.Collections.LowLevel.Unsafe;
  7. using Unity.Jobs;
  8. using UnityEngine.Internal;
  9. using Unity.Burst;
  10. using System.Runtime.CompilerServices;
  11. namespace Unity.Collections
  12. {
  13. /// <summary>
  14. /// An unordered, expandable set of unique values.
  15. /// </summary>
  16. /// <remarks>
  17. /// Not suitable for parallel write access. Use <see cref="NativeParallelHashSet{T}"/> instead.
  18. /// </remarks>
  19. /// <typeparam name="T">The type of the values.</typeparam>
  20. [StructLayout(LayoutKind.Sequential)]
  21. [NativeContainer]
  22. [DebuggerTypeProxy(typeof(NativeHashSetDebuggerTypeProxy<>))]
  23. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  24. public unsafe struct NativeHashSet<T>
  25. : INativeDisposable
  26. , IEnumerable<T> // Used by collection initializers.
  27. where T : unmanaged, IEquatable<T>
  28. {
  29. [NativeDisableUnsafePtrRestriction]
  30. internal HashMapHelper<T>* m_Data;
  31. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  32. internal AtomicSafetyHandle m_Safety;
  33. internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeHashSet<T>>();
  34. #endif
  35. /// <summary>
  36. /// Initializes and returns an instance of NativeParallelHashSet.
  37. /// </summary>
  38. /// <param name="initialCapacity">The number of values that should fit in the initial allocation.</param>
  39. /// <param name="allocator">The allocator to use.</param>
  40. public NativeHashSet(int initialCapacity, AllocatorManager.AllocatorHandle allocator)
  41. {
  42. m_Data = HashMapHelper<T>.Alloc(initialCapacity, 0, HashMapHelper<T>.kMinimumCapacity, allocator);
  43. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  44. m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
  45. if (UnsafeUtility.IsNativeContainerType<T>())
  46. AtomicSafetyHandle.SetNestedContainer(m_Safety, true);
  47. CollectionHelper.SetStaticSafetyId<NativeHashSet<T>>(ref m_Safety, ref s_staticSafetyId.Data);
  48. AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true);
  49. #endif
  50. }
  51. /// <summary>
  52. /// Whether this set is empty.
  53. /// </summary>
  54. /// <value>True if this set is empty or if the set has not been constructed.</value>
  55. public readonly bool IsEmpty
  56. {
  57. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  58. get
  59. {
  60. if (!IsCreated)
  61. {
  62. return true;
  63. }
  64. CheckRead();
  65. return m_Data->IsEmpty;
  66. }
  67. }
  68. /// <summary>
  69. /// Returns the current number of values in this set.
  70. /// </summary>
  71. /// <returns>The current number of values in this set.</returns>
  72. public readonly int Count
  73. {
  74. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  75. get
  76. {
  77. CheckRead();
  78. return m_Data->Count;
  79. }
  80. }
  81. /// <summary>
  82. /// The number of values that fit in the current allocation.
  83. /// </summary>
  84. /// <value>The number of values that fit in the current allocation.</value>
  85. /// <param name="value">A new capacity. Must be larger than current capacity.</param>
  86. public int Capacity
  87. {
  88. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  89. readonly get
  90. {
  91. CheckRead();
  92. return m_Data->Capacity;
  93. }
  94. set
  95. {
  96. CheckWrite();
  97. m_Data->Resize(value);
  98. }
  99. }
  100. /// <summary>
  101. /// Whether this set has been allocated (and not yet deallocated).
  102. /// </summary>
  103. /// <value>True if this set has been allocated (and not yet deallocated).</value>
  104. public readonly bool IsCreated
  105. {
  106. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  107. get => m_Data != null && m_Data->IsCreated;
  108. }
  109. /// <summary>
  110. /// Releases all resources (memory and safety handles).
  111. /// </summary>
  112. public void Dispose()
  113. {
  114. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  115. if (!AtomicSafetyHandle.IsDefaultValue(m_Safety))
  116. {
  117. AtomicSafetyHandle.CheckExistsAndThrow(m_Safety);
  118. }
  119. #endif
  120. if (!IsCreated)
  121. {
  122. return;
  123. }
  124. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  125. CollectionHelper.DisposeSafetyHandle(ref m_Safety);
  126. #endif
  127. HashMapHelper<T>.Free(m_Data);
  128. m_Data = null;
  129. }
  130. /// <summary>
  131. /// Creates and schedules a job that will dispose this set.
  132. /// </summary>
  133. /// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
  134. /// <returns>The handle of a new job that will dispose this set.</returns>
  135. public JobHandle Dispose(JobHandle inputDeps)
  136. {
  137. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  138. if (!AtomicSafetyHandle.IsDefaultValue(m_Safety))
  139. {
  140. AtomicSafetyHandle.CheckExistsAndThrow(m_Safety);
  141. }
  142. #endif
  143. if (!IsCreated)
  144. {
  145. return inputDeps;
  146. }
  147. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  148. var jobHandle = new NativeHashMapDisposeJob { Data = new NativeHashMapDispose { m_HashMapData = (UnsafeHashMap<int, int>*)m_Data, m_Safety = m_Safety } }.Schedule(inputDeps);
  149. AtomicSafetyHandle.Release(m_Safety);
  150. #else
  151. var jobHandle = new NativeHashMapDisposeJob { Data = new NativeHashMapDispose { m_HashMapData = (UnsafeHashMap<int, int>*)m_Data } }.Schedule(inputDeps);
  152. #endif
  153. m_Data = null;
  154. return jobHandle;
  155. }
  156. /// <summary>
  157. /// Removes all values.
  158. /// </summary>
  159. /// <remarks>Does not change the capacity.</remarks>
  160. public void Clear()
  161. {
  162. CheckWrite();
  163. m_Data->Clear();
  164. }
  165. /// <summary>
  166. /// Adds a new value (unless it is already present).
  167. /// </summary>
  168. /// <param name="item">The value to add.</param>
  169. /// <returns>True if the value was not already present.</returns>
  170. public bool Add(T item)
  171. {
  172. CheckWrite();
  173. return -1 != m_Data->TryAdd(item);
  174. }
  175. /// <summary>
  176. /// Removes a particular value.
  177. /// </summary>
  178. /// <param name="item">The value to remove.</param>
  179. /// <returns>True if the value was present.</returns>
  180. public bool Remove(T item)
  181. {
  182. CheckWrite();
  183. return -1 != m_Data->TryRemove(item);
  184. }
  185. /// <summary>
  186. /// Returns true if a particular value is present.
  187. /// </summary>
  188. /// <param name="item">The item to look up.</param>
  189. /// <returns>True if the value was present.</returns>
  190. public bool Contains(T item)
  191. {
  192. CheckRead();
  193. return -1 != m_Data->Find(item);
  194. }
  195. /// <summary>
  196. /// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
  197. /// </summary>
  198. public void TrimExcess()
  199. {
  200. CheckWrite();
  201. m_Data->TrimExcess();
  202. }
  203. /// <summary>
  204. /// Returns an array with a copy of this set's values (in no particular order).
  205. /// </summary>
  206. /// <param name="allocator">The allocator to use.</param>
  207. /// <returns>An array with a copy of the set's values.</returns>
  208. public NativeArray<T> ToNativeArray(AllocatorManager.AllocatorHandle allocator)
  209. {
  210. CheckRead();
  211. return m_Data->GetKeyArray(allocator);
  212. }
  213. /// <summary>
  214. /// Returns an enumerator over the values of this set.
  215. /// </summary>
  216. /// <returns>An enumerator over the values of this set.</returns>
  217. public Enumerator GetEnumerator()
  218. {
  219. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  220. AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety);
  221. var ash = m_Safety;
  222. AtomicSafetyHandle.UseSecondaryVersion(ref ash);
  223. #endif
  224. return new Enumerator
  225. {
  226. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  227. m_Safety = ash,
  228. #endif
  229. m_Enumerator = new HashMapHelper<T>.Enumerator(m_Data),
  230. };
  231. }
  232. /// <summary>
  233. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  234. /// </summary>
  235. /// <returns>Throws NotImplementedException.</returns>
  236. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  237. IEnumerator<T> IEnumerable<T>.GetEnumerator()
  238. {
  239. throw new NotImplementedException();
  240. }
  241. /// <summary>
  242. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  243. /// </summary>
  244. /// <returns>Throws NotImplementedException.</returns>
  245. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  246. IEnumerator IEnumerable.GetEnumerator()
  247. {
  248. throw new NotImplementedException();
  249. }
  250. /// <summary>
  251. /// An enumerator over the values of a set.
  252. /// </summary>
  253. /// <remarks>
  254. /// In an enumerator's initial state, <see cref="Current"/> is invalid.
  255. /// The first <see cref="MoveNext"/> call advances the enumerator to the first value.
  256. /// </remarks>
  257. [NativeContainer]
  258. [NativeContainerIsReadOnly]
  259. public struct Enumerator : IEnumerator<T>
  260. {
  261. [NativeDisableUnsafePtrRestriction]
  262. internal HashMapHelper<T>.Enumerator m_Enumerator;
  263. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  264. internal AtomicSafetyHandle m_Safety;
  265. #endif
  266. /// <summary>
  267. /// Does nothing.
  268. /// </summary>
  269. public void Dispose() { }
  270. /// <summary>
  271. /// Advances the enumerator to the next value.
  272. /// </summary>
  273. /// <returns>True if `Current` is valid to read after the call.</returns>
  274. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  275. public bool MoveNext()
  276. {
  277. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  278. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  279. #endif
  280. return m_Enumerator.MoveNext();
  281. }
  282. /// <summary>
  283. /// Resets the enumerator to its initial state.
  284. /// </summary>
  285. public void Reset()
  286. {
  287. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  288. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  289. #endif
  290. m_Enumerator.Reset();
  291. }
  292. /// <summary>
  293. /// The current value.
  294. /// </summary>
  295. /// <value>The current value.</value>
  296. public T Current
  297. {
  298. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  299. get
  300. {
  301. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  302. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  303. #endif
  304. return m_Enumerator.GetCurrentKey();
  305. }
  306. }
  307. /// <summary>
  308. /// Gets the element at the current position of the enumerator in the container.
  309. /// </summary>
  310. object IEnumerator.Current => Current;
  311. }
  312. /// <summary>
  313. /// Returns a readonly version of this NativeHashSet instance.
  314. /// </summary>
  315. /// <remarks>ReadOnly containers point to the same underlying data as the NativeHashSet it is made from.</remarks>
  316. /// <returns>ReadOnly instance for this.</returns>
  317. public ReadOnly AsReadOnly()
  318. {
  319. return new ReadOnly(ref this);
  320. }
  321. /// <summary>
  322. /// A read-only alias for the value of a NativeHashSet. Does not have its own allocated storage.
  323. /// </summary>
  324. [NativeContainer]
  325. [NativeContainerIsReadOnly]
  326. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  327. public struct ReadOnly
  328. : IEnumerable<T>
  329. {
  330. [NativeDisableUnsafePtrRestriction]
  331. internal HashMapHelper<T>* m_Data;
  332. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  333. AtomicSafetyHandle m_Safety;
  334. internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ReadOnly>();
  335. #endif
  336. internal ReadOnly(ref NativeHashSet<T> data)
  337. {
  338. m_Data = data.m_Data;
  339. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  340. m_Safety = data.m_Safety;
  341. CollectionHelper.SetStaticSafetyId<ReadOnly>(ref m_Safety, ref s_staticSafetyId.Data);
  342. #endif
  343. }
  344. /// <summary>
  345. /// Whether this hash set has been allocated (and not yet deallocated).
  346. /// </summary>
  347. /// <value>True if this hash set has been allocated (and not yet deallocated).</value>
  348. public readonly bool IsCreated
  349. {
  350. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  351. get
  352. {
  353. CheckRead();
  354. return m_Data->IsCreated;
  355. }
  356. }
  357. /// <summary>
  358. /// Whether this hash set is empty.
  359. /// </summary>
  360. /// <value>True if this hash set is empty or if the map has not been constructed.</value>
  361. public readonly bool IsEmpty
  362. {
  363. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  364. get
  365. {
  366. CheckRead();
  367. if (!m_Data->IsCreated)
  368. {
  369. return true;
  370. }
  371. return m_Data->IsEmpty;
  372. }
  373. }
  374. /// <summary>
  375. /// The current number of items in this hash set.
  376. /// </summary>
  377. /// <returns>The current number of items in this hash set.</returns>
  378. public readonly int Count
  379. {
  380. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  381. get
  382. {
  383. CheckRead();
  384. return m_Data->Count;
  385. }
  386. }
  387. /// <summary>
  388. /// The number of items that fit in the current allocation.
  389. /// </summary>
  390. /// <value>The number of items that fit in the current allocation.</value>
  391. public readonly int Capacity
  392. {
  393. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  394. get
  395. {
  396. CheckRead();
  397. return m_Data->Capacity;
  398. }
  399. }
  400. /// <summary>
  401. /// Returns true if a given item is present in this hash set.
  402. /// </summary>
  403. /// <param name="item">The item to look up.</param>
  404. /// <returns>True if the item was present.</returns>
  405. public readonly bool Contains(T item)
  406. {
  407. CheckRead();
  408. return -1 != m_Data->Find(item);
  409. }
  410. /// <summary>
  411. /// Returns an array with a copy of all this hash set's items (in no particular order).
  412. /// </summary>
  413. /// <param name="allocator">The allocator to use.</param>
  414. /// <returns>An array with a copy of all this hash set's items (in no particular order).</returns>
  415. public readonly NativeArray<T> ToNativeArray(AllocatorManager.AllocatorHandle allocator)
  416. {
  417. CheckRead();
  418. return m_Data->GetKeyArray(allocator);
  419. }
  420. /// <summary>
  421. /// Returns an enumerator over the items of this hash set.
  422. /// </summary>
  423. /// <returns>An enumerator over the items of this hash set.</returns>
  424. public readonly Enumerator GetEnumerator()
  425. {
  426. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  427. AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety);
  428. var ash = m_Safety;
  429. AtomicSafetyHandle.UseSecondaryVersion(ref ash);
  430. #endif
  431. return new Enumerator
  432. {
  433. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  434. m_Safety = ash,
  435. #endif
  436. m_Enumerator = new HashMapHelper<T>.Enumerator(m_Data),
  437. };
  438. }
  439. /// <summary>
  440. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  441. /// </summary>
  442. /// <returns>Throws NotImplementedException.</returns>
  443. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  444. IEnumerator<T> IEnumerable<T>.GetEnumerator()
  445. {
  446. throw new NotImplementedException();
  447. }
  448. /// <summary>
  449. /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
  450. /// </summary>
  451. /// <returns>Throws NotImplementedException.</returns>
  452. /// <exception cref="NotImplementedException">Method is not implemented.</exception>
  453. IEnumerator IEnumerable.GetEnumerator()
  454. {
  455. throw new NotImplementedException();
  456. }
  457. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  458. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  459. readonly void CheckRead()
  460. {
  461. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  462. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  463. #endif
  464. }
  465. }
  466. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  467. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  468. readonly void CheckRead()
  469. {
  470. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  471. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  472. #endif
  473. }
  474. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  475. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  476. void CheckWrite()
  477. {
  478. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  479. AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety);
  480. #endif
  481. }
  482. }
  483. sealed internal unsafe class NativeHashSetDebuggerTypeProxy<T>
  484. where T : unmanaged, IEquatable<T>
  485. {
  486. HashMapHelper<T>* Data;
  487. public NativeHashSetDebuggerTypeProxy(NativeHashSet<T> data)
  488. {
  489. Data = data.m_Data;
  490. }
  491. public List<T> Items
  492. {
  493. get
  494. {
  495. if (Data == null)
  496. {
  497. return default;
  498. }
  499. var result = new List<T>();
  500. using (var items = Data->GetKeyArray(Allocator.Temp))
  501. {
  502. for (var k = 0; k < items.Length; ++k)
  503. {
  504. result.Add(items[k]);
  505. }
  506. }
  507. return result;
  508. }
  509. }
  510. }
  511. }