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