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.Collections.NotBurstCompatible; 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. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public struct NativeParallelMultiHashMapIterator where TKey : unmanaged { internal TKey key; internal int NextEntryIndex; internal int EntryIndex; /// /// Returns the entry index. /// /// The entry index. [MethodImpl(MethodImplOptions.AggressiveInlining)] 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<,>))] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })] public unsafe struct NativeParallelMultiHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : unmanaged, IEquatable where TValue : unmanaged { internal UnsafeParallelMultiHashMap m_MultiHashMapData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #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 = default; Initialize(capacity, ref allocator); } [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })] internal void Initialize(int capacity, ref U allocator) where U : unmanaged, AllocatorManager.IAllocator { m_MultiHashMapData = new UnsafeParallelMultiHashMap(capacity, allocator.Handle); #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = CollectionHelper.CreateSafetyHandle(allocator.ToAllocator); 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 } /// /// Whether this hash map is empty. /// /// True if the hash map is empty or if the hash map has not been constructed. public readonly bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { 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 readonly 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 { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly 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 readonly bool IsCreated => m_MultiHashMapData.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_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. 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_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] [GenerateTestsForBurstCompatibility(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 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 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 = 1 }; } /// /// 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 byte 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. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { //Avoids going beyond the end of the collection. if (isFirst == 1) { isFirst = 0; 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 = 1; /// /// The current value. /// /// The current value. public TValue Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return 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. [MethodImpl(MethodImplOptions.AggressiveInlining)] 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 readonly KeyValue Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.GetCurrent(); } } object IEnumerator.Current => Current; } /// /// 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_MultiHashMapData, ash); #else return new ReadOnly(m_MultiHashMapData); #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 UnsafeParallelMultiHashMap m_MultiHashMapData; #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); [GenerateTestsForBurstCompatibility(CompileTarget = GenerateTestsForBurstCompatibilityAttribute.BurstCompatibleCompileTarget.Editor)] internal ReadOnly(UnsafeParallelMultiHashMap container, AtomicSafetyHandle safety) { m_MultiHashMapData = container; m_Safety = safety; CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); } #else internal ReadOnly(UnsafeParallelMultiHashMap container) { m_MultiHashMapData = container; } #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_MultiHashMapData.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_MultiHashMapData.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_MultiHashMapData.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_MultiHashMapData.Capacity; } } /// /// 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 readonly 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 readonly 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. public readonly bool ContainsKey(TKey key) { CheckRead(); return m_MultiHashMapData.ContainsKey(key); } /// /// 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_MultiHashMapData.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_MultiHashMapData.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_MultiHashMapData.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. /// /// 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(); } } [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 } } internal sealed class NativeParallelMultiHashMapDebuggerTypeProxy where TKey : unmanaged, IEquatable where TValue : unmanaged { NativeParallelMultiHashMap m_Target; public NativeParallelMultiHashMapDebuggerTypeProxy(NativeParallelMultiHashMap target) { m_Target = target; } public List>> Items { get { var result = new List>>(); (NativeArray, int) keys = default; using(NativeParallelHashMap uniques = new NativeParallelHashMap(m_Target.Count(),Allocator.Temp)) { var enumerator = m_Target.GetEnumerator(); while(enumerator.MoveNext()) uniques.TryAdd(enumerator.Current.Key,default); keys.Item1 = uniques.GetKeyArray(Allocator.Temp); keys.Item2 = keys.Item1.Length; } 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; } } } /// /// Extension methods for NativeParallelMultiHashMap. /// [GenerateTestsForBurstCompatibility] public unsafe static class NativeParallelMultiHashMapExtensions { [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int), typeof(AllocatorManager.AllocatorHandle) })] internal static void Initialize(ref this NativeParallelMultiHashMap container, int capacity, ref U allocator) where TKey : unmanaged, IEquatable where TValue : unmanaged where U : unmanaged, AllocatorManager.IAllocator { container.m_MultiHashMapData = new UnsafeParallelMultiHashMap(capacity, allocator.Handle); #if ENABLE_UNITY_COLLECTIONS_CHECKS container.m_Safety = CollectionHelper.CreateSafetyHandle(allocator.Handle); CollectionHelper.SetStaticSafetyId>(ref container.m_Safety, ref NativeParallelMultiHashMap.s_staticSafetyId.Data); #endif } } }