A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Diagnostics;
5using System.Runtime.CompilerServices;
6using System.Runtime.InteropServices;
7using Unity.Jobs;
8
9namespace Unity.Collections.LowLevel.Unsafe
10{
11 /// <summary>
12 /// An unordered, expandable set of unique values.
13 /// </summary>
14 /// <typeparam name="T">The type of the values.</typeparam>
15 [StructLayout(LayoutKind.Sequential)]
16 [DebuggerTypeProxy(typeof(UnsafeHashSetDebuggerTypeProxy<>))]
17 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
18 public unsafe struct UnsafeHashSet<T>
19 : INativeDisposable
20 , IEnumerable<T> // Used by collection initializers.
21 where T : unmanaged, IEquatable<T>
22 {
23 internal HashMapHelper<T> m_Data;
24
25 /// <summary>
26 /// Initializes and returns an instance of NativeParallelHashSet.
27 /// </summary>
28 /// <param name="initialCapacity">The number of values that should fit in the initial allocation.</param>
29 /// <param name="allocator">The allocator to use.</param>
30 public UnsafeHashSet(int initialCapacity, AllocatorManager.AllocatorHandle allocator)
31 {
32 m_Data = default;
33 m_Data.Init(initialCapacity, 0, HashMapHelper<T>.kMinimumCapacity, allocator);
34 }
35
36 /// <summary>
37 /// Whether this set is empty.
38 /// </summary>
39 /// <value>True if this set is empty or if the set has not been constructed.</value>
40 public readonly bool IsEmpty
41 {
42 [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 get => !IsCreated || m_Data.IsEmpty;
44 }
45
46 /// <summary>
47 /// Returns the current number of values in this set.
48 /// </summary>
49 /// <returns>The current number of values in this set.</returns>
50 public readonly int Count
51 {
52 [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 get => m_Data.Count;
54 }
55
56 /// <summary>
57 /// The number of values that fit in the current allocation.
58 /// </summary>
59 /// <value>The number of values that fit in the current allocation.</value>
60 /// <param name="value">A new capacity. Must be larger than current capacity.</param>
61 public int Capacity
62 {
63 [MethodImpl(MethodImplOptions.AggressiveInlining)]
64 readonly get => m_Data.Capacity;
65 set => m_Data.Resize(value);
66 }
67
68 /// <summary>
69 /// Whether this set has been allocated (and not yet deallocated).
70 /// </summary>
71 /// <value>True if this set has been allocated (and not yet deallocated).</value>
72 public readonly bool IsCreated
73 {
74 [MethodImpl(MethodImplOptions.AggressiveInlining)]
75 get => m_Data.IsCreated;
76 }
77
78 /// <summary>
79 /// Releases all resources (memory and safety handles).
80 /// </summary>
81 public void Dispose()
82 {
83 if (!IsCreated)
84 {
85 return;
86 }
87
88 m_Data.Dispose();
89 }
90
91 /// <summary>
92 /// Creates and schedules a job that will dispose this set.
93 /// </summary>
94 /// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
95 /// <returns>The handle of a new job that will dispose this set.</returns>
96 public JobHandle Dispose(JobHandle inputDeps)
97 {
98 if (!IsCreated)
99 {
100 return inputDeps;
101 }
102
103 var jobHandle = new UnsafeDisposeJob { Ptr = m_Data.Ptr, Allocator = m_Data.Allocator }.Schedule(inputDeps);
104 m_Data.Ptr = null;
105
106 return jobHandle;
107 }
108
109 /// <summary>
110 /// Removes all values.
111 /// </summary>
112 /// <remarks>Does not change the capacity.</remarks>
113 public void Clear()
114 {
115 m_Data.Clear();
116 }
117
118 /// <summary>
119 /// Adds a new value (unless it is already present).
120 /// </summary>
121 /// <param name="item">The value to add.</param>
122 /// <returns>True if the value was not already present.</returns>
123 public bool Add(T item)
124 {
125 return -1 != m_Data.TryAdd(item);
126 }
127
128 /// <summary>
129 /// Removes a particular value.
130 /// </summary>
131 /// <param name="item">The value to remove.</param>
132 /// <returns>True if the value was present.</returns>
133 public bool Remove(T item)
134 {
135 return -1 != m_Data.TryRemove(item);
136 }
137
138 /// <summary>
139 /// Returns true if a particular value is present.
140 /// </summary>
141 /// <param name="item">The value to check for.</param>
142 /// <returns>True if the value was present.</returns>
143 public bool Contains(T item)
144 {
145 return -1 != m_Data.Find(item);
146 }
147
148 /// <summary>
149 /// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
150 /// </summary>
151 public void TrimExcess() => m_Data.TrimExcess();
152
153 /// <summary>
154 /// Returns an array with a copy of this set's values (in no particular order).
155 /// </summary>
156 /// <param name="allocator">The allocator to use.</param>
157 /// <returns>An array with a copy of the set's values.</returns>
158 public NativeArray<T> ToNativeArray(AllocatorManager.AllocatorHandle allocator)
159 {
160 return m_Data.GetKeyArray(allocator);
161 }
162
163 /// <summary>
164 /// Returns an enumerator over the values of this set.
165 /// </summary>
166 /// <returns>An enumerator over the values of this set.</returns>
167 public Enumerator GetEnumerator()
168 {
169 fixed (HashMapHelper<T>* data = &m_Data)
170 {
171 return new Enumerator { m_Enumerator = new HashMapHelper<T>.Enumerator(data) };
172 }
173 }
174
175 /// <summary>
176 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
177 /// </summary>
178 /// <returns>Throws NotImplementedException.</returns>
179 /// <exception cref="NotImplementedException">Method is not implemented.</exception>
180 IEnumerator<T> IEnumerable<T>.GetEnumerator()
181 {
182 throw new NotImplementedException();
183 }
184
185 /// <summary>
186 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
187 /// </summary>
188 /// <returns>Throws NotImplementedException.</returns>
189 /// <exception cref="NotImplementedException">Method is not implemented.</exception>
190 IEnumerator IEnumerable.GetEnumerator()
191 {
192 throw new NotImplementedException();
193 }
194
195 /// <summary>
196 /// An enumerator over the values of a set.
197 /// </summary>
198 /// <remarks>
199 /// In an enumerator's initial state, <see cref="Current"/> is invalid.
200 /// The first <see cref="MoveNext"/> call advances the enumerator to the first value.
201 /// </remarks>
202 public struct Enumerator : IEnumerator<T>
203 {
204 internal HashMapHelper<T>.Enumerator m_Enumerator;
205
206 /// <summary>
207 /// Does nothing.
208 /// </summary>
209 public void Dispose() { }
210
211 /// <summary>
212 /// Advances the enumerator to the next value.
213 /// </summary>
214 /// <returns>True if `Current` is valid to read after the call.</returns>
215 [MethodImpl(MethodImplOptions.AggressiveInlining)]
216 public bool MoveNext() => m_Enumerator.MoveNext();
217
218 /// <summary>
219 /// Resets the enumerator to its initial state.
220 /// </summary>
221 public void Reset() => m_Enumerator.Reset();
222
223 /// <summary>
224 /// The current value.
225 /// </summary>
226 /// <value>The current value.</value>
227 public T Current
228 {
229 [MethodImpl(MethodImplOptions.AggressiveInlining)]
230 get => m_Enumerator.m_Data->Keys[m_Enumerator.m_Index];
231 }
232
233 /// <summary>
234 /// Gets the element at the current position of the enumerator in the container.
235 /// </summary>
236 object IEnumerator.Current => Current;
237 }
238
239 /// <summary>
240 /// Returns a readonly version of this UnsafeHashMap instance.
241 /// </summary>
242 /// <remarks>ReadOnly containers point to the same underlying data as the UnsafeHashMap it is made from.</remarks>
243 /// <returns>ReadOnly instance for this.</returns>
244 public ReadOnly AsReadOnly()
245 {
246 return new ReadOnly(ref m_Data);
247 }
248
249 /// <summary>
250 /// A read-only alias for the value of a UnsafeHashSet. Does not have its own allocated storage.
251 /// </summary>
252 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
253 public struct ReadOnly
254 : IEnumerable<T>
255 {
256 internal HashMapHelper<T> m_Data;
257
258 internal ReadOnly(ref HashMapHelper<T> data)
259 {
260 m_Data = data;
261 }
262
263 /// <summary>
264 /// Whether this hash map has been allocated (and not yet deallocated).
265 /// </summary>
266 /// <value>True if this hash map has been allocated (and not yet deallocated).</value>
267 public readonly bool IsCreated
268 {
269 [MethodImpl(MethodImplOptions.AggressiveInlining)]
270 get => m_Data.IsCreated;
271 }
272
273 /// <summary>
274 /// Whether this hash set is empty.
275 /// </summary>
276 /// <value>True if this hash set is empty or if the set has not been constructed.</value>
277 public readonly bool IsEmpty
278 {
279 [MethodImpl(MethodImplOptions.AggressiveInlining)]
280 get => m_Data.IsEmpty;
281 }
282
283 /// <summary>
284 /// The current number of key-value pairs in this hash map.
285 /// </summary>
286 /// <returns>The current number of key-value pairs in this hash map.</returns>
287 public readonly int Count
288 {
289 [MethodImpl(MethodImplOptions.AggressiveInlining)]
290 get => m_Data.Count;
291 }
292
293 /// <summary>
294 /// The number of key-value pairs that fit in the current allocation.
295 /// </summary>
296 /// <value>The number of key-value pairs that fit in the current allocation.</value>
297 public readonly int Capacity
298 {
299 [MethodImpl(MethodImplOptions.AggressiveInlining)]
300 get => m_Data.Capacity;
301 }
302
303 /// <summary>
304 /// Returns true if a particular value is present.
305 /// </summary>
306 /// <param name="item">The item to look up.</param>
307 /// <returns>True if the item was present.</returns>
308 public readonly bool Contains(T item)
309 {
310 return -1 != m_Data.Find(item);
311 }
312
313 /// <summary>
314 /// Returns an array with a copy of this set's values (in no particular order).
315 /// </summary>
316 /// <param name="allocator">The allocator to use.</param>
317 /// <returns>An array with a copy of the set's values.</returns>
318 public readonly NativeArray<T> ToNativeArray(AllocatorManager.AllocatorHandle allocator)
319 {
320 return m_Data.GetKeyArray(allocator);
321 }
322
323 /// <summary>
324 /// Returns an enumerator over the key-value pairs of this hash map.
325 /// </summary>
326 /// <returns>An enumerator over the key-value pairs of this hash map.</returns>
327 public readonly Enumerator GetEnumerator()
328 {
329 fixed (HashMapHelper<T>* data = &m_Data)
330 {
331 return new Enumerator { m_Enumerator = new HashMapHelper<T>.Enumerator(data) };
332 }
333 }
334
335 /// <summary>
336 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
337 /// </summary>
338 /// <returns>Throws NotImplementedException.</returns>
339 /// <exception cref="NotImplementedException">Method is not implemented.</exception>
340 IEnumerator<T> IEnumerable<T>.GetEnumerator()
341 {
342 throw new NotImplementedException();
343 }
344
345 /// <summary>
346 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
347 /// </summary>
348 /// <returns>Throws NotImplementedException.</returns>
349 /// <exception cref="NotImplementedException">Method is not implemented.</exception>
350 IEnumerator IEnumerable.GetEnumerator()
351 {
352 throw new NotImplementedException();
353 }
354 }
355 }
356
357 sealed internal class UnsafeHashSetDebuggerTypeProxy<T>
358 where T : unmanaged, IEquatable<T>
359 {
360 HashMapHelper<T> Data;
361
362 public UnsafeHashSetDebuggerTypeProxy(UnsafeHashSet<T> data)
363 {
364 Data = data.m_Data;
365 }
366
367 public List<T> Items
368 {
369 get
370 {
371 var result = new List<T>();
372 using (var keys = Data.GetKeyArray(Allocator.Temp))
373 {
374 for (var k = 0; k < keys.Length; ++k)
375 {
376 result.Add(keys[k]);
377 }
378 }
379
380 return result;
381 }
382 }
383 }
384}