using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; namespace Unity.Collections { /// /// The keys and values of a hash map copied into two parallel arrays. /// /// For each key-value pair copied from the hash map, the key is stored in `Keys[i]` while the value is stored in `Values[i]` (for the same `i`). /// /// NativeKeyValueArrays is not actually itself a native collection: it contains a NativeArray for the keys and a NativeArray for the values, /// but a NativeKeyValueArrays does not have its own safety handles. /// The type of the keys. /// The type of the values. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public struct NativeKeyValueArrays : INativeDisposable where TKey : unmanaged where TValue : unmanaged { /// /// The keys. /// /// The keys. The key at `Keys[i]` is paired with the value at `Values[i]`. public NativeArray Keys; /// /// The values. /// /// The values. The value at `Values[i]` is paired with the key at `Keys[i]`. public NativeArray Values; /// /// The number of key-value pairs. /// /// The number of key-value pairs. public int Length => Keys.Length; /// /// Initializes and returns an instance of NativeKeyValueArrays. /// /// The number of keys-value pairs. /// The allocator to use. /// Whether newly allocated bytes should be zeroed out. public NativeKeyValueArrays(int length, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options) { Keys = CollectionHelper.CreateNativeArray(length, allocator, options); Values = CollectionHelper.CreateNativeArray(length, allocator, options); } /// /// Releases all resources (memory and safety handles). /// public void Dispose() { Keys.Dispose(); Values.Dispose(); } /// /// Creates and schedules a job that will dispose this collection's key and value arrays. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this collection's key and value arrays. public JobHandle Dispose(JobHandle inputDeps) { return Keys.Dispose(Values.Dispose(inputDeps)); } } /// /// An unordered, expandable associative array. /// /// The type of the keys. /// The type of the values. [StructLayout(LayoutKind.Sequential)] [NativeContainer] [DebuggerDisplay("Count = {m_HashMapData.Count()}, Capacity = {m_HashMapData.Capacity}, IsCreated = {m_HashMapData.IsCreated}, IsEmpty = {IsEmpty}")] [DebuggerTypeProxy(typeof(NativeParallelHashMapDebuggerTypeProxy<,>))] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public unsafe struct NativeParallelHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : unmanaged, IEquatable where TValue : unmanaged { internal UnsafeParallelHashMap m_HashMapData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #endif /// /// Initializes and returns an instance of NativeParallelHashMap. /// /// The number of key-value pairs that should fit in the initial allocation. /// The allocator to use. public NativeParallelHashMap(int capacity, AllocatorManager.AllocatorHandle allocator) { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = CollectionHelper.CreateSafetyHandle(allocator); if (UnsafeUtility.IsNativeContainerType() || UnsafeUtility.IsNativeContainerType()) AtomicSafetyHandle.SetNestedContainer(m_Safety, true); CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); #endif m_HashMapData = new UnsafeParallelHashMap(capacity, allocator); } /// /// Whether this hash map is empty. /// /// True if this hash map 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_HashMapData.IsEmpty; } } /// /// The current number of key-value pairs in this hash map. /// /// The current number of key-value pairs in this hash map. public int Count() { CheckRead(); return m_HashMapData.Count(); } /// /// The number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. /// A new capacity. Must be larger than the current capacity. /// Thrown if `value` is less than the current capacity. public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get { CheckRead(); return m_HashMapData.Capacity; } set { CheckWrite(); m_HashMapData.Capacity = value; } } /// /// Removes all key-value pairs. /// /// Does not change the capacity. public void Clear() { CheckWrite(); m_HashMapData.Clear(); } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying the hash map. /// The key to add. /// The value to add. /// True if the key-value pair was added. public bool TryAdd(TKey key, TValue item) { CheckWrite(); return UnsafeParallelHashMapBase.TryAdd(m_HashMapData.m_Buffer, key, item, false, m_HashMapData.m_AllocatorLabel); } /// /// Adds a new key-value pair. /// /// If the key is already present, this method throws without modifying the hash map. /// The key to add. /// The value to add. /// Thrown if the key was already present. public void Add(TKey key, TValue item) { CheckWrite(); var added = UnsafeParallelHashMapBase.TryAdd(m_HashMapData.m_Buffer, key, item, false, m_HashMapData.m_AllocatorLabel); if (!added) { ThrowKeyAlreadyAdded(key); } } /// /// Removes a key-value pair. /// /// The key to remove. /// True if a key-value pair was removed. public bool Remove(TKey key) { CheckWrite(); return m_HashMapData.Remove(key); } /// /// Returns the value associated with a key. /// /// The key to look up. /// Outputs the value associated with the key. Outputs default if the key was not present. /// True if the key was present. public bool TryGetValue(TKey key, out TValue item) { CheckRead(); return m_HashMapData.TryGetValue(key, out item); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present. public bool ContainsKey(TKey key) { CheckRead(); return m_HashMapData.ContainsKey(key); } /// /// Gets and sets values by key. /// /// Getting a key that is not present will throw. Setting a key that is not already present will add the key. /// The key to look up. /// The value associated with the key. /// For getting, thrown if the key was not present. public TValue this[TKey key] { get { CheckRead(); TValue res; if (m_HashMapData.TryGetValue(key, out res)) { return res; } ThrowKeyNotPresent(key); return default; } set { CheckWrite(); m_HashMapData[key] = value; } } /// /// Whether this hash map has been allocated (and not yet deallocated). /// /// True if this hash map has been allocated (and not yet deallocated). public readonly bool IsCreated => m_HashMapData.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 m_HashMapData.Dispose(); } /// /// Creates and schedules a job that will dispose this hash map. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this hash map. 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 UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_HashMapData.m_Buffer, m_AllocatorLabel = m_HashMapData.m_AllocatorLabel, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_HashMapData.m_Buffer, m_AllocatorLabel = m_HashMapData.m_AllocatorLabel } }.Schedule(inputDeps); #endif m_HashMapData.m_Buffer = null; return jobHandle; } /// /// Returns an array with a copy of all this hash map's keys (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's keys (in no particular order). public NativeArray GetKeyArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetKeyArray(allocator); } /// /// Returns an array with a copy of all this hash map's values (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's values (in no particular order). public NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetValueArray(allocator); } /// /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values. /// /// The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`. /// The allocator to use. /// A NativeKeyValueArrays with a copy of all this hash map's keys and values. public NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetKeyValueArrays(allocator); } /// /// Returns a parallel writer for this hash map. /// /// A parallel writer for this hash map. public ParallelWriter AsParallelWriter() { ParallelWriter writer; writer.m_Writer = m_HashMapData.AsParallelWriter(); #if ENABLE_UNITY_COLLECTIONS_CHECKS writer.m_Safety = m_Safety; CollectionHelper.SetStaticSafetyId(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data); #endif return writer; } /// /// Returns a readonly version of this NativeParallelHashMap instance. /// /// ReadOnly containers point to the same underlying data as the NativeParallelHashMap it is made from. /// ReadOnly instance for this. public ReadOnly AsReadOnly() { #if ENABLE_UNITY_COLLECTIONS_CHECKS var ash = m_Safety; return new ReadOnly(m_HashMapData, ash); #else return new ReadOnly(m_HashMapData); #endif } /// /// A read-only alias for the value of a NativeParallelHashMap. Does not have its own allocated storage. /// [NativeContainer] [NativeContainerIsReadOnly] [DebuggerTypeProxy(typeof(NativeParallelHashMapDebuggerTypeProxy<,>))] [DebuggerDisplay("Count = {m_HashMapData.Count()}, Capacity = {m_HashMapData.Capacity}, IsCreated = {m_HashMapData.IsCreated}, IsEmpty = {IsEmpty}")] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public struct ReadOnly : IEnumerable> { internal UnsafeParallelHashMap m_HashMapData; #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); [GenerateTestsForBurstCompatibility(CompileTarget = GenerateTestsForBurstCompatibilityAttribute.BurstCompatibleCompileTarget.Editor)] internal ReadOnly(UnsafeParallelHashMap hashMapData, AtomicSafetyHandle safety) { m_HashMapData = hashMapData; m_Safety = safety; CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); } #else internal ReadOnly(UnsafeParallelHashMap hashMapData) { m_HashMapData = hashMapData; } #endif /// /// Whether this hash map has been allocated (and not yet deallocated). /// /// True if this hash map has been allocated (and not yet deallocated). public readonly bool IsCreated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_HashMapData.IsCreated; } /// /// Whether this hash map is empty. /// /// True if this hash map 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_HashMapData.IsEmpty; } } /// /// The current number of key-value pairs in this hash map. /// /// The current number of key-value pairs in this hash map. public readonly int Count() { CheckRead(); return m_HashMapData.Count(); } /// /// The number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. public readonly int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { CheckRead(); return m_HashMapData.Capacity; } } /// /// Returns the value associated with a key. /// /// The key to look up. /// Outputs the value associated with the key. Outputs default if the key was not present. /// True if the key was present. public readonly bool TryGetValue(TKey key, out TValue item) { CheckRead(); return m_HashMapData.TryGetValue(key, out item); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present. public readonly bool ContainsKey(TKey key) { CheckRead(); return m_HashMapData.ContainsKey(key); } /// /// Gets values by key. /// /// Getting a key that is not present will throw. /// The key to look up. /// The value associated with the key. /// For getting, thrown if the key was not present. public readonly TValue this[TKey key] { get { CheckRead(); TValue res; if (m_HashMapData.TryGetValue(key, out res)) { return res; } ThrowKeyNotPresent(key); return default; } } /// /// Returns an array with a copy of all this hash map's keys (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's keys (in no particular order). public readonly NativeArray GetKeyArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetKeyArray(allocator); } /// /// Returns an array with a copy of all this hash map's values (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's values (in no particular order). public readonly NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetValueArray(allocator); } /// /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values. /// /// The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`. /// The allocator to use. /// A NativeKeyValueArrays with a copy of all this hash map's keys and values. public readonly NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetKeyValueArrays(allocator); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] readonly void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] readonly void ThrowKeyNotPresent(TKey key) { throw new ArgumentException($"Key: {key} is not present in the NativeParallelHashMap."); } /// /// Returns an enumerator over the key-value pairs of this hash map. /// /// An enumerator over the key-value pairs of this hash map. 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 UnsafeParallelHashMapDataEnumerator(m_HashMapData.m_Buffer), }; } /// /// 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(); } } /// /// A parallel writer for a NativeParallelHashMap. /// /// /// Use to create a parallel writer for a NativeParallelHashMap. /// [NativeContainer] [NativeContainerIsAtomicWriteOnly] [DebuggerDisplay("Capacity = {m_Writer.Capacity}")] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public unsafe struct ParallelWriter { internal UnsafeParallelHashMap.ParallelWriter m_Writer; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); #endif /// /// Returns the index of the current thread. /// /// In a job, each thread gets its own copy of the ParallelWriter struct, and the job system assigns /// each copy the index of its thread. /// The index of the current thread. public int ThreadIndex => m_Writer.m_ThreadIndex; /// **Obsolete. Use instead. [Obsolete("'m_ThreadIndex' has been deprecated; use 'ThreadIndex' instead. (UnityUpgradable) -> ThreadIndex")] public int m_ThreadIndex => m_Writer.m_ThreadIndex; /// /// The number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. public readonly int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Writer.Capacity; } } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying this hash map. /// The key to add. /// The value to add. /// True if the key-value pair was added. public bool TryAdd(TKey key, TValue item) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif return m_Writer.TryAdd(key, item); } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying this hash map. /// The key to add. /// The value to add. /// The thread index which must be set by a field from a job struct with the attribute. /// True if the key-value pair was added. internal bool TryAdd(TKey key, TValue item, int threadIndexOverride) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif return m_Writer.TryAdd(key, item, threadIndexOverride); } } /// /// Returns an enumerator over the key-value pairs of this hash map. /// /// An enumerator over the key-value pairs of this hash map. 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 UnsafeParallelHashMapDataEnumerator(m_HashMapData.m_Buffer), }; } /// /// 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 key-value pairs of a hash map. /// /// /// In an enumerator's initial state, is not valid to read. /// From this state, the first call advances the enumerator to the first key-value pair. /// [NativeContainer] [NativeContainerIsReadOnly] public struct Enumerator : IEnumerator> { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif internal UnsafeParallelHashMapDataEnumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next key-value pair. /// /// True if 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 key-value pair. /// /// The current key-value pair. public KeyValue Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_Enumerator.GetCurrent(); } object IEnumerator.Current => Current; } [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 } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] void ThrowKeyNotPresent(TKey key) { throw new ArgumentException($"Key: {key} is not present in the NativeParallelHashMap."); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] void ThrowKeyAlreadyAdded(TKey key) { throw new ArgumentException("An item with the same key has already been added", nameof(key)); } } internal sealed class NativeParallelHashMapDebuggerTypeProxy where TKey : unmanaged, IEquatable where TValue : unmanaged { UnsafeParallelHashMap m_Target; public NativeParallelHashMapDebuggerTypeProxy(NativeParallelHashMap target) { m_Target = target.m_HashMapData; } internal NativeParallelHashMapDebuggerTypeProxy(NativeParallelHashMap.ReadOnly target) { m_Target = target.m_HashMapData; } public List> Items { get { var result = new List>(); using (var kva = m_Target.GetKeyValueArrays(Allocator.Temp)) { for (var i = 0; i < kva.Length; ++i) { result.Add(new Pair(kva.Keys[i], kva.Values[i])); } } return result; } } } }