using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using Unity.Jobs; using UnityEngine.Assertions; namespace Unity.Collections.LowLevel.Unsafe { /// /// An unordered, expandable associative array. Each key can have more than one associated value. /// /// /// Unlike a regular UnsafeHashMap, an UnsafeParallelMultiHashMap 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)] [DebuggerTypeProxy(typeof(UnsafeParallelMultiHashMapDebuggerTypeProxy<,>))] [BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public unsafe struct UnsafeParallelMultiHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : struct, IEquatable where TValue : struct { [NativeDisableUnsafePtrRestriction] internal UnsafeParallelHashMapData* m_Buffer; internal AllocatorManager.AllocatorHandle m_AllocatorLabel; /// /// Initializes and returns an instance of UnsafeParallelMultiHashMap. /// /// The number of key-value pairs that should fit in the initial allocation. /// The allocator to use. public UnsafeParallelMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator) { m_AllocatorLabel = allocator; // Bucket size if bigger to reduce collisions UnsafeParallelHashMapData.AllocateHashMap(capacity, capacity * 2, allocator, out m_Buffer); Clear(); } /// /// Whether this hash map is empty. /// /// True if this hash map is empty or the hash map has not been constructed. public bool IsEmpty => !IsCreated || UnsafeParallelHashMapData.IsEmpty(m_Buffer); /// /// 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() { if (m_Buffer->allocatedIndexLength <= 0) { return 0; } return UnsafeParallelHashMapData.GetCount(m_Buffer); } /// /// 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 { UnsafeParallelHashMapData* data = m_Buffer; return data->keyCapacity; } set { UnsafeParallelHashMapData* data = m_Buffer; UnsafeParallelHashMapData.ReallocateHashMap(data, value, UnsafeParallelHashMapData.GetBucketSize(value), m_AllocatorLabel); } } /// /// Removes all key-value pairs. /// /// Does not change the capacity. public void Clear() { UnsafeParallelHashMapBase.Clear(m_Buffer); } /// /// 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) { UnsafeParallelHashMapBase.TryAdd(m_Buffer, key, item, true, m_AllocatorLabel); } /// /// 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) { return UnsafeParallelHashMapBase.Remove(m_Buffer, key, true); } /// /// Removes all key-value pairs with a particular key and a particular value. /// /// Removes all key-value pairs which have a particular key and which *also have* a particular value. /// In other words: (key *AND* value) rather than (key *OR* value). /// The type of the value. /// The key of the key-value pairs to remove. /// The value of the key-value pairs to remove. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public void Remove(TKey key, TValueEQ value) where TValueEQ : struct, IEquatable { UnsafeParallelHashMapBase.RemoveKeyValue(m_Buffer, key, value); } /// /// 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) { UnsafeParallelHashMapBase.Remove(m_Buffer, 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) { return UnsafeParallelHashMapBase.TryGetFirstValueAtomic(m_Buffer, 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) { return UnsafeParallelHashMapBase.TryGetNextValueAtomic(m_Buffer, 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) { return UnsafeParallelHashMapBase.SetValue(m_Buffer, ref it, ref item); } /// /// 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_Buffer != null; /// /// Releases all resources (memory and safety handles). /// public void Dispose() { UnsafeParallelHashMapData.DeallocateHashMap(m_Buffer, m_AllocatorLabel); m_Buffer = null; } /// /// 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) { var jobHandle = new UnsafeParallelHashMapDisposeJob { Data = m_Buffer, Allocator = m_AllocatorLabel }.Schedule(inputDeps); 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) { var result = CollectionHelper.CreateNativeArray(Count(), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetKeyArray(m_Buffer, result); return result; } /// /// 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) { var result = CollectionHelper.CreateNativeArray(Count(), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetValueArray(m_Buffer, result); return result; } /// /// 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) { var result = new NativeKeyValueArrays(Count(), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetKeyValueArrays(m_Buffer, result); return result; } /// /// 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) { 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 UnsafeParallelMultiHashMap 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 a parallel writer for this hash map. /// /// A parallel writer for this hash map. public ParallelWriter AsParallelWriter() { ParallelWriter writer; #if UNITY_DOTSRUNTIME writer.m_ThreadIndex = -1; // aggressively check that code-gen has patched the ThreadIndex #else writer.m_ThreadIndex = 0; #endif writer.m_Buffer = m_Buffer; return writer; } /// /// A parallel writer for an UnsafeParallelMultiHashMap. /// /// /// Use to create a parallel writer for a UnsafeParallelMultiHashMap. /// [NativeContainerIsAtomicWriteOnly] [BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public unsafe struct ParallelWriter { [NativeDisableUnsafePtrRestriction] internal UnsafeParallelHashMapData* m_Buffer; [NativeSetThreadIndex] internal int 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 { return m_Buffer->keyCapacity; } } /// /// 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) { Assert.IsTrue(m_ThreadIndex >= 0); UnsafeParallelHashMapBase.AddAtomicMulti(m_Buffer, key, item, m_ThreadIndex); } } /// /// 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() { return new KeyValueEnumerator { m_Enumerator = new UnsafeParallelHashMapDataEnumerator(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. /// public struct KeyValueEnumerator : IEnumerator> { 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 bool MoveNext() => m_Enumerator.MoveNext(); /// /// Resets the enumerator to its initial state. /// public void Reset() => m_Enumerator.Reset(); /// /// The current key-value pair. /// /// The current key-value pair. public KeyValue Current => m_Enumerator.GetCurrent(); object IEnumerator.Current => Current; } } internal sealed class UnsafeParallelMultiHashMapDebuggerTypeProxy where TKey : struct, IEquatable, IComparable where TValue : struct { #if !NET_DOTS UnsafeParallelMultiHashMap m_Target; public UnsafeParallelMultiHashMapDebuggerTypeProxy(UnsafeParallelMultiHashMap target) { m_Target = target; } public static (NativeArray, int) GetUniqueKeyArray(ref UnsafeParallelMultiHashMap hashMap, AllocatorManager.AllocatorHandle allocator) { var withDuplicates = hashMap.GetKeyArray(allocator); withDuplicates.Sort(); int uniques = withDuplicates.Unique(); return (withDuplicates, uniques); } public List>> Items { get { var result = new List>>(); var keys = GetUniqueKeyArray(ref m_Target, 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 } }