using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Burst; using System.Runtime.CompilerServices; namespace Unity.Collections { /// /// An unordered, expandable set of unique values. /// /// /// Not suitable for parallel write access. Use instead. /// /// The type of the values. [StructLayout(LayoutKind.Sequential)] [NativeContainer] [DebuggerTypeProxy(typeof(NativeHashSetDebuggerTypeProxy<>))] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public unsafe struct NativeHashSet : INativeDisposable , IEnumerable // Used by collection initializers. where T : unmanaged, IEquatable { [NativeDisableUnsafePtrRestriction] internal HashMapHelper* m_Data; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #endif /// /// Initializes and returns an instance of NativeParallelHashSet. /// /// The number of values that should fit in the initial allocation. /// The allocator to use. public NativeHashSet(int initialCapacity, AllocatorManager.AllocatorHandle allocator) { m_Data = HashMapHelper.Alloc(initialCapacity, 0, HashMapHelper.kMinimumCapacity, allocator); #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = CollectionHelper.CreateSafetyHandle(allocator); if (UnsafeUtility.IsNativeContainerType()) AtomicSafetyHandle.SetNestedContainer(m_Safety, true); CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); #endif } /// /// Whether this set is empty. /// /// True if this set is empty or if the set has not been constructed. public readonly bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (!IsCreated) { return true; } CheckRead(); return m_Data->IsEmpty; } } /// /// Returns the current number of values in this set. /// /// The current number of values in this set. public readonly int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { CheckRead(); return m_Data->Count; } } /// /// The number of values that fit in the current allocation. /// /// The number of values that fit in the current allocation. /// A new capacity. Must be larger than current capacity. public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get { CheckRead(); return m_Data->Capacity; } set { CheckWrite(); m_Data->Resize(value); } } /// /// Whether this set has been allocated (and not yet deallocated). /// /// True if this set has been allocated (and not yet deallocated). public readonly bool IsCreated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_Data != null && m_Data->IsCreated; } /// /// Releases all resources (memory and safety handles). /// public void Dispose() { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (!AtomicSafetyHandle.IsDefaultValue(m_Safety)) { AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); } #endif if (!IsCreated) { return; } #if ENABLE_UNITY_COLLECTIONS_CHECKS CollectionHelper.DisposeSafetyHandle(ref m_Safety); #endif HashMapHelper.Free(m_Data); m_Data = null; } /// /// Creates and schedules a job that will dispose this set. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this set. public JobHandle Dispose(JobHandle inputDeps) { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (!AtomicSafetyHandle.IsDefaultValue(m_Safety)) { AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); } #endif if (!IsCreated) { return inputDeps; } #if ENABLE_UNITY_COLLECTIONS_CHECKS var jobHandle = new NativeHashMapDisposeJob { Data = new NativeHashMapDispose { m_HashMapData = (UnsafeHashMap*)m_Data, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new NativeHashMapDisposeJob { Data = new NativeHashMapDispose { m_HashMapData = (UnsafeHashMap*)m_Data } }.Schedule(inputDeps); #endif m_Data = null; return jobHandle; } /// /// Removes all values. /// /// Does not change the capacity. public void Clear() { CheckWrite(); m_Data->Clear(); } /// /// Adds a new value (unless it is already present). /// /// The value to add. /// True if the value was not already present. public bool Add(T item) { CheckWrite(); return -1 != m_Data->TryAdd(item); } /// /// Removes a particular value. /// /// The value to remove. /// True if the value was present. public bool Remove(T item) { CheckWrite(); return -1 != m_Data->TryRemove(item); } /// /// Returns true if a particular value is present. /// /// The item to look up. /// True if the value was present. public bool Contains(T item) { CheckRead(); return -1 != m_Data->Find(item); } /// /// Sets the capacity to match what it would be if it had been originally initialized with all its entries. /// public void TrimExcess() { CheckWrite(); m_Data->TrimExcess(); } /// /// Returns an array with a copy of this set's values (in no particular order). /// /// The allocator to use. /// An array with a copy of the set's values. public NativeArray ToNativeArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_Data->GetKeyArray(allocator); } /// /// Returns an enumerator over the values of this set. /// /// An enumerator over the values of this set. public Enumerator GetEnumerator() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety); var ash = m_Safety; AtomicSafetyHandle.UseSecondaryVersion(ref ash); #endif return new Enumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = ash, #endif m_Enumerator = new HashMapHelper.Enumerator(m_Data), }; } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// An enumerator over the values of a set. /// /// /// In an enumerator's initial state, is invalid. /// The first call advances the enumerator to the first value. /// [NativeContainer] [NativeContainerIsReadOnly] public struct Enumerator : IEnumerator { [NativeDisableUnsafePtrRestriction] internal HashMapHelper.Enumerator m_Enumerator; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next value. /// /// True if `Current` is valid to read after the call. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.MoveNext(); } /// /// Resets the enumerator to its initial state. /// public void Reset() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif m_Enumerator.Reset(); } /// /// The current value. /// /// The current value. public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.GetCurrentKey(); } } /// /// Gets the element at the current position of the enumerator in the container. /// object IEnumerator.Current => Current; } /// /// Returns a readonly version of this NativeHashSet instance. /// /// ReadOnly containers point to the same underlying data as the NativeHashSet it is made from. /// ReadOnly instance for this. public ReadOnly AsReadOnly() { return new ReadOnly(ref this); } /// /// A read-only alias for the value of a NativeHashSet. Does not have its own allocated storage. /// [NativeContainer] [NativeContainerIsReadOnly] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public struct ReadOnly : IEnumerable { [NativeDisableUnsafePtrRestriction] internal HashMapHelper* m_Data; #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); #endif internal ReadOnly(ref NativeHashSet data) { m_Data = data.m_Data; #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = data.m_Safety; CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); #endif } /// /// Whether this hash set has been allocated (and not yet deallocated). /// /// True if this hash set has been allocated (and not yet deallocated). public readonly bool IsCreated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_Data != null && m_Data->IsCreated; } /// /// Whether this hash set is empty. /// /// True if this hash set is empty or if the map has not been constructed. public readonly bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (!IsCreated) { return true; } CheckRead(); return m_Data->IsEmpty; } } /// /// The current number of items in this hash set. /// /// The current number of items in this hash set. public readonly int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { CheckRead(); return m_Data->Count; } } /// /// The number of items that fit in the current allocation. /// /// The number of items that fit in the current allocation. public readonly int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { CheckRead(); return m_Data->Capacity; } } /// /// Returns true if a given item is present in this hash set. /// /// The item to look up. /// True if the item was present. public readonly bool Contains(T item) { CheckRead(); return -1 != m_Data->Find(item); } /// /// Returns an array with a copy of all this hash set's items (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash set's items (in no particular order). public readonly NativeArray ToNativeArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_Data->GetKeyArray(allocator); } /// /// Returns an enumerator over the items of this hash set. /// /// An enumerator over the items of this hash set. public readonly Enumerator GetEnumerator() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety); var ash = m_Safety; AtomicSafetyHandle.UseSecondaryVersion(ref ash); #endif return new Enumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = ash, #endif m_Enumerator = new HashMapHelper.Enumerator(m_Data), }; } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] [MethodImpl(MethodImplOptions.AggressiveInlining)] void CheckWrite() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif } } sealed internal unsafe class NativeHashSetDebuggerTypeProxy where T : unmanaged, IEquatable { HashMapHelper* Data; public NativeHashSetDebuggerTypeProxy(NativeHashSet data) { Data = data.m_Data; } public List Items { get { if (Data == null) { return default; } var result = new List(); using (var items = Data->GetKeyArray(Allocator.Temp)) { for (var k = 0; k < items.Length; ++k) { result.Add(items[k]); } } return result; } } } }