using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; namespace Unity.Collections { /// /// An iterator over all values associated with an individual key in a multi hash map. /// /// The iteration order over the values associated with a key is an implementation detail. Do not rely upon any particular ordering. /// The type of the keys. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public struct NativeParallelMultiHashMapIterator where TKey : struct { internal TKey key; internal int NextEntryIndex; internal int EntryIndex; /// /// Returns the entry index. /// /// The entry index. public int GetEntryIndex() => EntryIndex; } /// /// An unordered, expandable associative array. Each key can have more than one associated value. /// /// /// Unlike a regular NativeParallelHashMap, a NativeParallelMultiHashMap can store multiple key-value pairs with the same key. /// /// The keys are not deduplicated: two key-value pairs with the same key are stored as fully separate key-value pairs. /// /// The type of the keys. /// The type of the values. [StructLayout(LayoutKind.Sequential)] [NativeContainer] [DebuggerTypeProxy(typeof(NativeParallelMultiHashMapDebuggerTypeProxy<,>))] [BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public unsafe struct NativeParallelMultiHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : struct, IEquatable where TValue : struct { internal UnsafeParallelMultiHashMap m_MultiHashMapData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #if REMOVE_DISPOSE_SENTINEL #else [NativeSetClassTypeToNullOnSchedule] internal DisposeSentinel m_DisposeSentinel; #endif #endif /// /// Returns a newly allocated multi hash map. /// /// The number of key-value pairs that should fit in the initial allocation. /// The allocator to use. public NativeParallelMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator) : this(capacity, allocator, 2) { } [BurstCompatible(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })] internal void Initialize(int capacity, ref U allocator, int disposeSentinelStackDepth) where U : unmanaged, AllocatorManager.IAllocator { m_MultiHashMapData = new UnsafeParallelMultiHashMap(capacity, allocator.Handle); #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL m_Safety = CollectionHelper.CreateSafetyHandle(allocator); #else if (allocator.IsCustomAllocator) { m_Safety = AtomicSafetyHandle.Create(); m_DisposeSentinel = null; } else { DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, disposeSentinelStackDepth, allocator.ToAllocator); } #endif CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); #endif } NativeParallelMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator, int disposeSentinelStackDepth) { this = default; Initialize(capacity, ref allocator, disposeSentinelStackDepth); } /// /// Whether this hash map is empty. /// /// True if the hash map is empty or if the hash map has not been constructed. public bool IsEmpty { get { CheckRead(); return m_MultiHashMapData.IsEmpty; } } /// /// Returns the current number of key-value pairs in this hash map. /// /// Key-value pairs with matching keys are counted as separate, individual pairs. /// The current number of key-value pairs in this hash map. public int Count() { CheckRead(); return m_MultiHashMapData.Count(); } /// /// Returns 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 { get { CheckRead(); return m_MultiHashMapData.Capacity; } set { CheckWrite(); m_MultiHashMapData.Capacity = value; } } /// /// Removes all key-value pairs. /// /// Does not change the capacity. public void Clear() { CheckWrite(); m_MultiHashMapData.Clear(); } /// /// Adds a new key-value pair. /// /// /// If a key-value pair with this key is already present, an additional separate key-value pair is added. /// /// The key to add. /// The value to add. public void Add(TKey key, TValue item) { CheckWrite(); m_MultiHashMapData.Add(key, item); } /// /// Removes a key and its associated value(s). /// /// The key to remove. /// The number of removed key-value pairs. If the key was not present, returns 0. public int Remove(TKey key) { CheckWrite(); return m_MultiHashMapData.Remove(key); } /// /// Removes a single key-value pair. /// /// An iterator representing the key-value pair to remove. /// Thrown if the iterator is invalid. public void Remove(NativeParallelMultiHashMapIterator it) { CheckWrite(); m_MultiHashMapData.Remove(it); } /// /// Gets an iterator for a key. /// /// The key. /// Outputs the associated value represented by the iterator. /// Outputs an iterator. /// True if the key was present. public bool TryGetFirstValue(TKey key, out TValue item, out NativeParallelMultiHashMapIterator it) { CheckRead(); return m_MultiHashMapData.TryGetFirstValue(key, out item, out it); } /// /// Advances an iterator to the next value associated with its key. /// /// Outputs the next value. /// A reference to the iterator to advance. /// True if the key was present and had another value. public bool TryGetNextValue(out TValue item, ref NativeParallelMultiHashMapIterator it) { CheckRead(); return m_MultiHashMapData.TryGetNextValue(out item, ref it); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present in this hash map. public bool ContainsKey(TKey key) { return TryGetFirstValue(key, out var temp0, out var temp1); } /// /// Returns the number of values associated with a given key. /// /// The key to look up. /// The number of values associated with the key. Returns 0 if the key was not present. public int CountValuesForKey(TKey key) { if (!TryGetFirstValue(key, out var value, out var iterator)) { return 0; } var count = 1; while (TryGetNextValue(out value, ref iterator)) { count++; } return count; } /// /// Sets a new value for an existing key-value pair. /// /// The new value. /// The iterator representing a key-value pair. /// True if a value was overwritten. public bool SetValue(TValue item, NativeParallelMultiHashMapIterator it) { CheckWrite(); return m_MultiHashMapData.SetValue(item, it); } /// /// Whether this hash map has been allocated (and not yet deallocated). /// /// True if this hash map has been allocated (and not yet deallocated). public bool IsCreated => m_MultiHashMapData.IsCreated; /// /// Releases all resources (memory and safety handles). /// public void Dispose() { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL CollectionHelper.DisposeSafetyHandle(ref m_Safety); #else DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); #endif #endif m_MultiHashMapData.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. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL #else // [DeallocateOnJobCompletion] is not supported, but we want the deallocation // to happen in a thread. DisposeSentinel needs to be cleared on main thread. // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling // will check that no jobs are writing to the container). DisposeSentinel.Clear(ref m_DisposeSentinel); #endif var jobHandle = new UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_MultiHashMapData.m_Buffer, m_AllocatorLabel = m_MultiHashMapData.m_AllocatorLabel, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_MultiHashMapData.m_Buffer, m_AllocatorLabel = m_MultiHashMapData.m_AllocatorLabel } }.Schedule(inputDeps); #endif m_MultiHashMapData.m_Buffer = null; return jobHandle; } /// /// Returns an array with a copy of all the keys (in no particular order). /// /// A key with *N* values is included *N* times in the array. /// /// Use `GetUniqueKeyArray` of instead if you only want one occurrence of each key. /// The allocator to use. /// An array with a copy of all the keys (in no particular order). public NativeArray GetKeyArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_MultiHashMapData.GetKeyArray(allocator); } /// /// Returns an array with a copy of all the values (in no particular order). /// /// The values are not deduplicated. If you sort the returned array, /// you can use to remove duplicate values. /// The allocator to use. /// An array with a copy of all the values (in no particular order). public NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_MultiHashMapData.GetValueArray(allocator); } /// /// Returns a NativeKeyValueArrays with a copy of all the keys and values (in no particular order). /// /// A key with *N* values is included *N* times in the array. /// /// The allocator to use. /// A NativeKeyValueArrays with a copy of all the keys and values (in no particular order). public NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_MultiHashMapData.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_MultiHashMapData.AsParallelWriter(); #if ENABLE_UNITY_COLLECTIONS_CHECKS writer.m_Safety = m_Safety; CollectionHelper.SetStaticSafetyId(ref writer.m_Safety, ref s_staticSafetyId.Data); #endif return writer; } /// /// A parallel writer for a NativeParallelMultiHashMap. /// /// /// Use to create a parallel writer for a NativeParallelMultiHashMap. /// [NativeContainer] [NativeContainerIsAtomicWriteOnly] [BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public unsafe struct ParallelWriter { internal UnsafeParallelMultiHashMap.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 m_ThreadIndex => m_Writer.m_ThreadIndex; /// /// Returns 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 int Capacity { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Writer.Capacity; } } /// /// Adds a new key-value pair. /// /// /// If a key-value pair with this key is already present, an additional separate key-value pair is added. /// /// The key to add. /// The value to add. public void Add(TKey key, TValue item) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_Writer.Add(key, item); } } /// /// Returns an enumerator over the values of an individual key. /// /// The key to get an enumerator for. /// An enumerator over the values of a key. public Enumerator GetValuesForKey(TKey key) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return new Enumerator { hashmap = this, key = key, isFirst = true }; } /// /// An enumerator over the values of an individual key in a multi hash map. /// /// /// In an enumerator's initial state, is not valid to read. /// The first call advances the enumerator to the first value of the key. /// public struct Enumerator : IEnumerator { internal NativeParallelMultiHashMap hashmap; internal TKey key; internal bool isFirst; TValue value; NativeParallelMultiHashMapIterator iterator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next value of the key. /// /// True if is valid to read after the call. public bool MoveNext() { //Avoids going beyond the end of the collection. if (isFirst) { isFirst = false; return hashmap.TryGetFirstValue(key, out value, out iterator); } return hashmap.TryGetNextValue(out value, ref iterator); } /// /// Resets the enumerator to its initial state. /// public void Reset() => isFirst = true; /// /// The current value. /// /// The current value. public TValue Current => value; object IEnumerator.Current => Current; /// /// Returns this enumerator. /// /// This enumerator. public Enumerator GetEnumerator() { return this; } } /// /// Returns an enumerator over the key-value pairs of this hash map. /// /// A key with *N* values is visited by the enumerator *N* times. /// An enumerator over the key-value pairs of this hash map. public KeyValueEnumerator GetEnumerator() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety); var ash = m_Safety; AtomicSafetyHandle.UseSecondaryVersion(ref ash); #endif return new KeyValueEnumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = ash, #endif m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_MultiHashMapData.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 multi hash map. /// /// A key with *N* values is visited by the enumerator *N* times. /// /// In an enumerator's initial state, is not valid to read. /// The first call advances the enumerator to the first key-value pair. /// [NativeContainer] [NativeContainerIsReadOnly] public struct KeyValueEnumerator : 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. public unsafe 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 { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.GetCurrent(); } } object IEnumerator.Current => Current; } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckWrite() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif } } internal sealed class NativeParallelMultiHashMapDebuggerTypeProxy where TKey : struct, IEquatable, IComparable where TValue : struct { #if !NET_DOTS NativeParallelMultiHashMap m_Target; public NativeParallelMultiHashMapDebuggerTypeProxy(NativeParallelMultiHashMap target) { m_Target = target; } public List>> Items { get { var result = new List>>(); var keys = m_Target.GetUniqueKeyArray(Allocator.Temp); using (keys.Item1) { for (var k = 0; k < keys.Item2; ++k) { var values = new List(); if (m_Target.TryGetFirstValue(keys.Item1[k], out var value, out var iterator)) { do { values.Add(value); } while (m_Target.TryGetNextValue(out value, ref iterator)); } result.Add(new ListPair>(keys.Item1[k], values)); } } return result; } } #endif } [BurstCompatible] public unsafe static class NativeParallelMultiHashMapExtensions { [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int), typeof(AllocatorManager.AllocatorHandle) })] internal static void Initialize(ref this NativeParallelMultiHashMap container, int capacity, ref U allocator, int disposeSentinelStackDepth = 2) where TKey : struct, IEquatable where TValue : struct where U : unmanaged, AllocatorManager.IAllocator { container.m_MultiHashMapData = new UnsafeParallelMultiHashMap(capacity, allocator.Handle); #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL container.m_Safety = CollectionHelper.CreateSafetyHandle(allocator.Handle); #else if (allocator.IsCustomAllocator) { container.m_Safety = AtomicSafetyHandle.Create(); container.m_DisposeSentinel = null; } else { DisposeSentinel.Create(out container.m_Safety, out container.m_DisposeSentinel, disposeSentinelStackDepth, allocator.ToAllocator); } #endif CollectionHelper.SetStaticSafetyId>(ref container.m_Safety, ref NativeParallelMultiHashMap.s_staticSafetyId.Data); #endif } } }