Search Unity

Hashmap in a blob asset

Discussion in 'Entity Component System' started by PublicEnumE, Nov 17, 2019.

  1. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I'm still wrapping my head around the proper usage of blobassets.

    I was wondering if one can store a NativeHashMap<T> in a blob asset, or if blob data has the same limitations as the data you can store inside an IComponentData.

    At a high level, my goal is to store an immutable lookup table on my entity, which I can pass into jobs.

    Thanks for any advice!
     
  2. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    49
    Blobs doesn't support it at least as it's currently implement but there's no limitation for "BlobHashMap<T>" to be implemented in the future
     
  3. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    Well the way to do it is to put the keys and values as separate arrays in a blob asset and then read it at runtime to a hashmap. The BlobAsset as kind of a stream, like other streams will not support all data structures. Not that it is not possible to support it but the intended use would be to read/write data from these linearly so they don't support them yet at least
     
  4. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    In theory, it is not too hard to implement an immutable hash map based on Hash Array Mapped Tries. But it require a tree structure so you have to flat tree into a continues blob to fit it into `BlobAsset` or allocate multiple `BlobAsset`s to represent tree structure. (LanguageExt.HashMap / LanguageExt.TrieMap)

    I don't understand your use case, but in my experience, `Entity` itself is kind of `Map` structure which similar to `Map<Type[typeof(IComponentData)], IComponentData>` or `Map<int[TypeIndex], IComponentData>`, so why don't you just store your data into several `IComponentData`?
     
  5. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    You could write your own naive "1st semester computer science" implementation on top of a list / array with a hash function and linear search on collision for the time being, and optimize from there.
     
  6. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    If you are good with pointers UnsafeHashMap<TKey, TValue> and it's internal pointer UnsafeHashMapData* m_Buffer.
    Is a good point to store hashmap as Blob data.
    UnsafeHashMapData has several pointers, as data would be immutable in blob memory, UnsafeHashMapData can be re-allocated continually, and be stored as blob data. when used just set UnsafeHashMap<TKey, TValue>.m_Buffer to the UnsafeHashMapData you stored.
     
  7. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    I have got it working
    First, you need to has internal access to unity.collections; Please refer to this:
    https://forum.unity.com/threads/please-help-bug-in-unity-csproj-generation.868198/#post-5715838
    Then this world work:
    Code (CSharp):
    1.  
    2.     unsafe public struct HashMapBlob
    3.     {
    4.        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
    5.         public static void CheckAlignment(this int alignment)
    6.         {
    7.             var zeroAlignment = alignment == 0;
    8.             var powTwoAlignment = ((alignment - 1) & alignment) == 0;
    9.             var validAlignment = (!zeroAlignment) && powTwoAlignment;
    10.             if (!validAlignment)
    11.             {
    12.                 throw new ArgumentException($"Specified alignment must be non-zero positive power of two. Requested: {alignment}");
    13.             }
    14.         }
    15.         public static int Align(this int offset, int alignment)
    16.         {
    17.             CheckAlignment(alignment);
    18.             return (offset + (alignment - 1)) & -alignment;
    19.         }
    20.         public static readonly int k_HeaderSize = UnsafeUtility.SizeOf<HashMapBlob>().Align(JobsUtility.CacheLineSize);
    21.         unsafe internal static HashMapBlob* FromHashMapDataRaw<TKey, TValue>(UnsafeHashMapData* data, Allocator label)
    22.             where TKey : unmanaged, IEquatable<TKey>
    23.             where TValue : unmanaged
    24.         {
    25.             UnsafeHashMapData.IsBlittableAndThrow<TKey, TValue>();
    26.             var keyCapacity = data->keyCapacity;
    27.             var bucketCapacityMask = data->bucketCapacityMask;
    28.             var bucketCapacity = bucketCapacityMask + 1;
    29.             int dataSize = UnsafeHashMapData.CalculateDataSize<TKey, TValue>(keyCapacity, bucketCapacity, out var keyOffset, out var nextOffset, out var bucketOffset);
    30.             int totalSize = k_HeaderSize + dataSize;
    31.  
    32.             HashMapBlob* pBlob = (HashMapBlob*)UnsafeUtility.Malloc(totalSize, JobsUtility.CacheLineSize, label);
    33.             var pValueOut = (byte*)(pBlob) + k_HeaderSize;
    34.             var pKeysOut = pValueOut + keyOffset;
    35.             var pNextOut = pValueOut + nextOffset;
    36.             var pBucketsOut = pValueOut + bucketOffset;
    37.  
    38.             UnsafeUtility.MemCpy(pValueOut, data->values, keyCapacity * UnsafeUtility.SizeOf<TValue>());
    39.             UnsafeUtility.MemCpy(pKeysOut, data->keys, keyCapacity * UnsafeUtility.SizeOf<TKey>());
    40.             UnsafeUtility.MemCpy(pNextOut, data->next, keyCapacity * UnsafeUtility.SizeOf<int>());
    41.             UnsafeUtility.MemCpy(pBucketsOut, data->buckets, bucketCapacity * UnsafeUtility.SizeOf<int>());
    42.  
    43.             pBlob->m_TotalSize = totalSize;
    44.  
    45.             pBlob->m_KeyOffset = keyOffset;
    46.             pBlob->m_NextOffset = nextOffset;
    47.             pBlob->m_BucketOffset = bucketOffset;
    48.  
    49.             pBlob->m_keyCapacity = keyCapacity;
    50.             pBlob->m_bucketCapacityMask = bucketCapacityMask;
    51.             pBlob->m_allocatedIndexLength = math.min(data->allocatedIndexLength, data->keyCapacity);
    52.             pBlob->m_pBuffer = default;
    53.             return pBlob;
    54.         }
    55.  
    56.         unsafe internal static HashMapBlob* FromHashMapRaw<TKey, TValue>(UnsafeHashMap<TKey, TValue> map, Allocator label)
    57.             where TKey : unmanaged, IEquatable<TKey>
    58.             where TValue : unmanaged
    59.             => FromHashMapDataRaw<TKey, TValue>(map.m_Buffer, label);
    60.  
    61.         unsafe internal static HashMapBlob* FromHashMapRaw<TKey, TValue>(NativeHashMap<TKey, TValue> map, Allocator label)
    62.             where TKey : unmanaged, IEquatable<TKey>
    63.             where TValue : unmanaged
    64.             => FromHashMapDataRaw<TKey, TValue>(map.m_HashMapData.m_Buffer, label);
    65.  
    66.         unsafe internal static HashMapBlob* FromMultiMapRaw<TKey, TValue>(UnsafeMultiHashMap<TKey, TValue> map, Allocator label)
    67.             where TKey : unmanaged, IEquatable<TKey>
    68.             where TValue : unmanaged
    69.             => FromHashMapDataRaw<TKey, TValue>(map.m_Buffer, label);
    70.  
    71.         unsafe internal static HashMapBlob* FromMultiMapRaw<TKey, TValue>(NativeMultiHashMap<TKey, TValue> map, Allocator label)
    72.             where TKey : unmanaged, IEquatable<TKey>
    73.             where TValue : unmanaged
    74.             => FromHashMapDataRaw<TKey, TValue>(map.m_MultiHashMapData.m_Buffer, label);
    75.  
    76.         unsafe internal static HashMapBlob* FromHasSetRaw<T>(UnsafeHashSet<T> set, Allocator label)
    77.             where T : unmanaged, IEquatable<T>
    78.             => FromHashMapDataRaw<T, bool>(set.m_Data.m_Buffer, label);
    79.  
    80.         unsafe internal static HashMapBlob* FromHasSetRaw<T>(NativeHashSet<T> map, Allocator label)
    81.             where T : unmanaged, IEquatable<T>
    82.             => FromHashMapDataRaw<T, bool>(map.m_Data.m_HashMapData.m_Buffer, label);
    83.  
    84.         public void BuildUnsafeHashMapData()
    85.         {
    86.             m_pBuffer.values = PThis + k_HeaderSize;
    87.             m_pBuffer.keys = m_pBuffer.values + m_KeyOffset;
    88.             m_pBuffer.next = m_pBuffer.values + m_NextOffset;
    89.             m_pBuffer.buckets = m_pBuffer.values + m_BucketOffset;
    90.             m_pBuffer.keyCapacity = m_keyCapacity;
    91.             m_pBuffer.bucketCapacityMask = m_bucketCapacityMask;
    92.             m_pBuffer.allocatedIndexLength = m_allocatedIndexLength;
    93.             for (int tls = 0; tls < JobsUtility.MaxJobThreadCount; ++tls) { m_pBuffer.firstFreeTLS[tls * UnsafeHashMapData.IntsPerCacheLine] = -1; }
    94.         }
    95.  
    96.         unsafe internal static BlobAssetReference<HashMapBlob> FromHashMapData<TKey, TValue>(UnsafeHashMapData* data)
    97.             where TKey : unmanaged, IEquatable<TKey>
    98.             where TValue : unmanaged
    99.         {
    100.             var pBlob = FromHashMapDataRaw<TKey, TValue>(data, Allocator.Temp);
    101.             var asset = BlobAssetReference<HashMapBlob>.Create(pBlob, pBlob->m_TotalSize);
    102.             asset.Value.BuildUnsafeHashMapData();
    103.             return asset;
    104.         }
    105.  
    106.         unsafe public static BlobAssetReference<HashMapBlob> FromHashMap<TKey, TValue>(UnsafeHashMap<TKey, TValue> map)
    107.             where TKey : unmanaged, IEquatable<TKey>
    108.             where TValue : unmanaged
    109.             => FromHashMapData<TKey, TValue>(map.m_Buffer);
    110.  
    111.         unsafe public static BlobAssetReference<HashMapBlob> FromHashMap<TKey, TValue>(NativeHashMap<TKey, TValue> map)
    112.             where TKey : unmanaged, IEquatable<TKey>
    113.             where TValue : unmanaged
    114.             => FromHashMapData<TKey, TValue>(map.m_HashMapData.m_Buffer);
    115.  
    116.         unsafe public static BlobAssetReference<HashMapBlob> FromMultiMap<TKey, TValue>(UnsafeMultiHashMap<TKey, TValue> map)
    117.             where TKey : unmanaged, IEquatable<TKey>
    118.             where TValue : unmanaged
    119.             => FromHashMapData<TKey, TValue>(map.m_Buffer);
    120.  
    121.         unsafe public static BlobAssetReference<HashMapBlob> FromMultiMap<TKey, TValue>(NativeMultiHashMap<TKey, TValue> map)
    122.             where TKey : unmanaged, IEquatable<TKey>
    123.             where TValue : unmanaged
    124.             => FromHashMapData<TKey, TValue>(map.m_MultiHashMapData.m_Buffer);
    125.  
    126.         unsafe public static BlobAssetReference<HashMapBlob> FromHasSet<T>(UnsafeHashSet<T> set)
    127.             where T : unmanaged, IEquatable<T>
    128.             => FromHashMapData<T, bool>(set.m_Data.m_Buffer);
    129.  
    130.         unsafe public static BlobAssetReference<HashMapBlob> FromHasSet<T>(NativeHashSet<T> map)
    131.             where T : unmanaged, IEquatable<T>
    132.             => FromHashMapData<T, bool>(map.m_Data.m_HashMapData.m_Buffer);
    133.  
    134.         unsafe internal byte* PThis { get { fixed (void* pThis = &this) { return (byte*)pThis; } } }
    135.         unsafe internal UnsafeHashMapData* Buffer { get { fixed (UnsafeHashMapData* pBuf = &m_pBuffer) return pBuf; } }
    136.  
    137.         int m_TotalSize;
    138.         int m_KeyOffset;
    139.         int m_NextOffset;
    140.         int m_BucketOffset;
    141.         internal int m_keyCapacity;
    142.         internal int m_bucketCapacityMask;
    143.         internal int m_allocatedIndexLength;
    144.  
    145.         UnsafeHashMapData m_pBuffer;
    146.  
    147.         unsafe public UnsafeHashMap<TKey, TValue> AsHashMap<TKey, TValue>()
    148.             where TKey : unmanaged, IEquatable<TKey>
    149.             where TValue : unmanaged
    150.             => new UnsafeHashMap<TKey, TValue>() { m_Buffer = Buffer };
    151.  
    152.         unsafe public UnsafeMultiHashMap<TKey, TValue> AsMultiMap<TKey, TValue>()
    153.             where TKey : unmanaged, IEquatable<TKey>
    154.             where TValue : unmanaged
    155.             => new UnsafeMultiHashMap<TKey, TValue>() { m_Buffer = Buffer };
    156.  
    157.         public UnsafeHashSet<T> AsHashSet<T>() where T : unmanaged, IEquatable<T>
    158.             => new UnsafeHashSet<T>() { m_Data = AsHashMap<T, bool>() };
    159.     }

    Works with *HashMap *MultiHashMap *HashSet

    BuildUnsafeHashMapData() needs to be called whenever data is relocated, for example, move to BlobAssetStore.

    Content is read-only, never add remove or clear the hashmap converted from the blob. Doing so will corrupt internal data.
     
    Last edited: Sep 7, 2020
  8. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    This is not a correct way of implementing it. It will not work with serialized data. The whole point of blobs is to be relocatable...

    The correct way to implement it is to build on top of BlobArray structures. Reusing UnsafeHashmap code is not possible since it uses pointers, but it it should be straightforward to copy & adjust some of the code from unsafehashmap. It should be much simpler than the full implementation since you only need read / lookup functionality & the ability to copy data from a NativeHashMap.
     
    Nyanpas and Lieene-Guo like this.
  9. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    I am in fact rebuilding all "pointed" memory of UnsafeHashMapData as a contiguous memory block and recording all data array offsets. So it is a relocatable deep memcpy. when it is used as a *HasMap, UnsafeHashMapData is rebuilt by offsetting pointers inside of BlobAsset memory address. So it is working as long as it is read-only. When accessed from blob returning a different warping struct like "ReadonlyHashMap" with only read access would be better in this case, what I am doing is reusing UnsafeHashMap for simplicity.
     
    Last edited: Sep 7, 2020
  10. Thermos

    Thermos

    Joined:
    Feb 23, 2015
    Posts:
    148
    I wrote a BlobHashMap half years ago. Using similar extension API like allocating blobArray, can be easily convert from/to C# dictionary. Also can store in byte[], so it's a good way to implementing Monobehavior dictionary serialization.

    Core part:
    Code (CSharp):
    1. public struct BlobHashMap<TKey, TValue> where TKey : struct, IEquatable<TKey> where TValue : struct
    2.     {
    3.         [StructLayout(LayoutKind.Sequential)]
    4.         public struct Entry
    5.         {
    6.             public int next;
    7.             public TKey key;
    8.             public TValue value;
    9.         }
    10.  
    11.         internal BlobArray<int> buckets;
    12.         internal BlobArray<Entry> data;
    13.         internal int halfPV;
    14.  
    15.         public int Count
    16.         {
    17.             get { return data.Length; }
    18.         }
    19.  
    20.         public ref TValue Get(in TKey _key)
    21.         {
    22.             if (buckets.Length == -1) throw new Exception();
    23.  
    24.             if (halfPV <= 0 || Count <= 5)
    25.             {
    26.                 for (int i = 0, c = Count; i < c; ++i)
    27.                 {
    28.                     ref var entry = ref data[i];
    29.  
    30.                     if (entry.key.Equals(_key))
    31.                     {
    32.                         return ref entry.value;
    33.                     }
    34.                 }
    35.             }
    36.             else
    37.             {
    38.                 var bucketIndex = _key.GetHashCode() % halfPV;
    39.  
    40.                 bucketIndex = buckets[bucketIndex + halfPV];
    41.  
    42.                 while (bucketIndex != -1)
    43.                 {
    44.                     ref var entry = ref data[bucketIndex];
    45.                     ref var key = ref entry.key;
    46.                     if (key.Equals(_key))
    47.                     {
    48.                         return ref entry.value;
    49.                     }
    50.  
    51.                     bucketIndex = entry.next;
    52.                 }
    53.             }
    54.  
    55.             throw new KeyNotFoundException();
    56.         }
    57.  
    58.         public ref TValue Get(in string _key)
    59.         {
    60.             if (halfPV <= 0 || Count <= 5)
    61.             {
    62.                 for (int i = 0, c = Count; i < c; ++i)
    63.                 {
    64.                     ref var entry = ref data[i];
    65.  
    66.                     if (entry.key.Equals(_key))
    67.                     {
    68.                         return ref entry.value;
    69.                     }
    70.                 }
    71.             }
    72.             else
    73.             {
    74.                 var bucketIndex = _key.GetHashCode() % halfPV;
    75.  
    76.                 bucketIndex = buckets[bucketIndex + halfPV];
    77.  
    78.                 while (bucketIndex != -1)
    79.                 {
    80.                     ref var entry = ref data[bucketIndex];
    81.  
    82.                     if (entry.key.Equals(_key))
    83.                     {
    84.                         return ref entry.value;
    85.                     }
    86.  
    87.                     bucketIndex = entry.next;
    88.                 }
    89.             }
    90.  
    91.             throw new KeyNotFoundException();
    92.         }
    93.  
    94.         public bool ContainsKey(in TKey _key)
    95.         {
    96.             if (halfPV <= 0 || Count <= 5)
    97.             {
    98.                 for (int i = 0, c = Count; i < c; ++i)
    99.                 {
    100.                     ref var entry = ref data[i];
    101.                  
    102.                     if (entry.key.Equals(_key))
    103.                     {
    104.                         return true;
    105.                     }
    106.                 }
    107.             }
    108.             else
    109.             {
    110.                 var bucketIndex = _key.GetHashCode() % halfPV;
    111.  
    112.                 bucketIndex = buckets[bucketIndex + halfPV];
    113.  
    114.                 while (bucketIndex != -1)
    115.                 {
    116.                     ref var entry = ref data[bucketIndex];
    117.  
    118.                     if (entry.key.Equals(_key))
    119.                     {
    120.                         return true;
    121.                     }
    122.  
    123.                     bucketIndex = entry.next;
    124.                 }
    125.             }
    126.  
    127.             return false;
    128.         }
    129.  
    130.         public bool ContainsKey(in string _key)
    131.         {
    132.             if (halfPV <= 0 || Count <= 5)
    133.             {
    134.                 for (int i = 0, c = Count; i < c; ++i)
    135.                 {
    136.                     ref var entry = ref data[i];
    137.                  
    138.                     if (entry.key.Equals(_key))
    139.                     {
    140.                         return true;
    141.                     }
    142.                 }
    143.             }
    144.             else
    145.             {
    146.                 var bucketIndex = _key.GetHashCode() % halfPV;
    147.  
    148.                 bucketIndex = buckets[bucketIndex + halfPV];
    149.  
    150.                 while (bucketIndex != -1)
    151.                 {
    152.                     ref var entry = ref data[bucketIndex];
    153.  
    154.                     if (entry.key.Equals(_key))
    155.                     {
    156.                         return true;
    157.                     }
    158.  
    159.                     bucketIndex = entry.next;
    160.                 }
    161.             }
    162.  
    163.             return false;
    164.         }
    165.  
    166.         public bool TryGetValue(in TKey _key, out TValue _value)
    167.         {
    168.             if (halfPV <= 0 || Count <= 5)
    169.             {
    170.                 for (int i = 0, c = Count; i < c; ++i)
    171.                 {
    172.                     ref var entry = ref data[i];
    173.                  
    174.                     if (entry.key.Equals(_key))
    175.                     {
    176.                         _value = entry.value;
    177.                         return true;
    178.                     }
    179.                 }
    180.             }
    181.             else
    182.             {
    183.                 var bucketIndex = buckets[_key.GetHashCode() % halfPV + halfPV];
    184.  
    185.                 while (bucketIndex != -1)
    186.                 {
    187.                     ref var entry = ref data[bucketIndex];
    188.  
    189.                     if (entry.key.Equals(_key))
    190.                     {
    191.                         _value = entry.value;
    192.                         return true;
    193.                     }
    194.  
    195.                     bucketIndex = entry.next;
    196.                 }
    197.             }
    198.  
    199.             _value = default;
    200.             return false;
    201.         }
    202.  
    203.         public bool TryGetValue(in string _key, out TValue _value)
    204.         {
    205.             if (halfPV <= 0 || Count <= 5)
    206.             {
    207.                 for (int i = 0, c = Count; i < c; ++i)
    208.                 {
    209.                     ref var entry = ref data[i];
    210.                  
    211.                     if (entry.key.Equals(_key))
    212.                     {
    213.                         _value = entry.value;
    214.                         return true;
    215.                     }
    216.                 }
    217.             }
    218.             else
    219.             {
    220.                 var bucketIndex = buckets[_key.GetHashCode() % halfPV + halfPV];
    221.  
    222.                 while (bucketIndex != -1)
    223.                 {
    224.                     ref var entry = ref data[bucketIndex];
    225.  
    226.                     if (entry.key.Equals(_key))
    227.                     {
    228.                         _value = entry.value;
    229.                         return true;
    230.                     }
    231.  
    232.                     bucketIndex = entry.next;
    233.                 }
    234.             }
    235.  
    236.             _value = default;
    237.             return false;
    238.         }
    239.  
    240.         public void ForEach(BlobHashMapForEachCallback<TKey, TValue> _callback)
    241.         {
    242.             for (int i = 0, count = data.Length; i < count; ++i)
    243.             {
    244.                 ref var entry = ref data[i];
    245.                 _callback.Invoke(ref entry.key, ref entry.value);
    246.             }
    247.         }
    248.  
    249.         public NativeArray<TKey> GetKeys(Allocator _allocator = Allocator.Temp)
    250.         {
    251.             var length = data.Length;
    252.  
    253.             var array = new NativeArray<TKey>(length, _allocator, NativeArrayOptions.UninitializedMemory);
    254.             for (var i = 0; i < length; ++i)
    255.             {
    256.                 ref var entry = ref data[i];
    257.                 array[i] = entry.key;
    258.             }
    259.  
    260.             return array;
    261.         }
    262.     }
    Creation Part
    Code (CSharp):
    1.     public static class BlobHashMapExtensions
    2.     {
    3.         private static bool IsPrime(int n)
    4.         {
    5.             if (n < 2) return false;
    6.  
    7.             for (var i = n - 1; i > 1; i--)
    8.             {
    9.                 if (n % i == 0)
    10.                  
    11.                     return false;
    12.             }
    13.  
    14.  
    15.             return true;
    16.         }
    17.  
    18.         private static void InternalAllocateHashMap<TKey, TValue, TDataKey, TDataValue>(ref this BlobBuilder builder,
    19.             ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TDataKey, TDataValue> value,
    20.             BlobElementConverter<TKey, TValue, TDataKey, TDataValue> converter)
    21.             where TKey : struct, IEquatable<TKey> where TValue : struct
    22.         {
    23.             var count = value.Count;
    24.  
    25.             if (count == 0) return;
    26.  
    27.             var pV = 2;
    28.  
    29.             if (count > 2)
    30.             {
    31.                 for (int i = count - 1; i >= 0; --i)
    32.                 {
    33.                     if (IsPrime(i))
    34.                     {
    35.                         pV = i;
    36.                         break;
    37.                     }
    38.                 }
    39.             }
    40.  
    41.             var bucketArray = builder.Allocate(ref hashMap.buckets, pV);
    42.             var dataArray = builder.Allocate(ref hashMap.data, count);
    43.             var halfPv = pV / 2;
    44.             hashMap.halfPV = halfPv;
    45.  
    46.             for (int i = 0; i < pV; ++i)
    47.             {
    48.                 bucketArray[i] = -1;
    49.             }
    50.  
    51.             var entryIndex = 0;
    52.             foreach (var pair in value)
    53.             {
    54.                 var bucketIndex = pair.Key.GetHashCode() % halfPv + halfPv;
    55.  
    56.                 dataArray[entryIndex] = new BlobHashMap<TKey, TValue>.Entry
    57.                 {
    58.                     next = -1
    59.                 };
    60.  
    61.                 ref var a = ref dataArray[entryIndex];
    62.                 converter(ref builder, ref a, pair);
    63.  
    64.                 if (bucketArray[bucketIndex] == -1)
    65.                 {
    66.                     bucketArray[bucketIndex] = entryIndex;
    67.                 }
    68.                 else
    69.                 {
    70.                     var tempIndex = bucketArray[bucketIndex];
    71.  
    72.                     while (dataArray[tempIndex].next != -1)
    73.                     {
    74.                         tempIndex = dataArray[tempIndex].next;
    75.                     }
    76.  
    77.                     ref var v = ref dataArray[tempIndex];
    78.                     v.next = entryIndex;
    79.                 }
    80.  
    81.                 ++entryIndex;
    82.             }
    83.         }
    84.  
    85.         public static void DebugHashMap<TKey, TValue>(ref this BlobHashMap<TKey, TValue> _map)
    86.             where TKey : struct, IEquatable<TKey> where TValue : struct
    87.         {
    88.             _map.ForEach(((ref TKey _key, ref TValue _value) =>
    89.             {
    90.                 UnityEngine.Debug.Log($"Key = {_key}, Value = {_value.ToString()}");
    91.             }));
    92.         }
    93.  
    94.         public static void AllocateHashMap<TKey, TValue>(ref this BlobBuilder builder,
    95.             ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKey, TValue> value)
    96.             where TKey : struct, IEquatable<TKey> where TValue : struct
    97.         {
    98.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    99.                 (ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
    100.                     in KeyValuePair<TKey, TValue> _value) =>
    101.                 {
    102.                     _entry.key = _value.Key;
    103.                     _entry.value = _value.Value;
    104.                 });
    105.         }
    106.  
    107.         public static void AllocateHashMap<TKey, TValue>(ref this BlobBuilder builder,
    108.             ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKey, List<TValue>> value)
    109.             where TKey : struct, IEquatable<TKey> where TValue : struct
    110.         {
    111.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    112.                 (ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
    113.                     in KeyValuePair<TKey, List<TValue>> _value) =>
    114.                 {
    115.                     _entry.key = _value.Key;
    116.  
    117.                     if (_value.Value != null && _value.Value.Count > 0)
    118.                     {
    119.                         _builder.AllocateArray(ref _entry.value, _value.Value);
    120.                     }
    121.                 });
    122.         }
    123.      
    124.         public static void AllocateHashMap(ref this BlobBuilder builder,
    125.             ref BlobHashMap<BlobString, BlobString> hashMap, Dictionary<string, string> value)
    126.         {
    127.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    128.                 ((ref BlobBuilder _builder, ref BlobHashMap<BlobString, BlobString>.Entry _entry,
    129.                     in KeyValuePair<string, string> _value) =>
    130.                 {
    131.                     _builder.AllocateString(ref _entry.key, _value.Key);
    132.                     _builder.AllocateString(ref _entry.value, _value.Value);
    133.                 }));
    134.         }
    135.  
    136.         public static void AllocateHashMap(ref this BlobBuilder builder,
    137.             ref BlobHashMap<BlobString, BlobArray<BlobString>> hashMap, Dictionary<string, List<string>> value)
    138.         {
    139.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    140.                 ((ref BlobBuilder _builder, ref BlobHashMap<BlobString, BlobArray<BlobString>>.Entry _entry,
    141.                     in KeyValuePair<string, List<string>> _value) =>
    142.                 {
    143.                     _builder.AllocateString(ref _entry.key, _value.Key);
    144.                  
    145.                     var list = _value.Value;
    146.                     if (list != null && list.Count > 0)
    147.                     {
    148.                         var array = _builder.Allocate(ref _entry.value, list.Count);
    149.                         for (int i = 0; i < list.Count; ++i)
    150.                         {
    151.                             _builder.AllocateString(ref array[i], list[i]);
    152.                         }
    153.                     }
    154.                 }));
    155.         }
    156.  
    157.         public static void AllocateHashMap<TKey>(ref this BlobBuilder builder,
    158.             ref BlobHashMap<TKey, BlobString> hashMap, Dictionary<TKey, string> value)
    159.             where TKey : struct, IEquatable<TKey>
    160.         {
    161.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    162.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobString>.Entry _entry,
    163.                     in KeyValuePair<TKey, string> _value) =>
    164.                 {
    165.                     _entry.key = _value.Key;
    166.                     _builder.AllocateString(ref _entry.value, _value.Value);
    167.                 }));
    168.         }
    169.      
    170.         public static void AllocateHashMap<TKey>(ref this BlobBuilder builder,
    171.             ref BlobHashMap<TKey, BlobArray<BlobString>> hashMap, Dictionary<TKey, List<string>> value)
    172.             where TKey : struct, IEquatable<TKey>
    173.         {
    174.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    175.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<BlobString>>.Entry _entry,
    176.                     in KeyValuePair<TKey, List<string>> _value) =>
    177.                 {
    178.                     _entry.key = _value.Key;
    179.                  
    180.                     var list = _value.Value;
    181.                     if (list != null && list.Count > 0)
    182.                     {
    183.                         var array = _builder.Allocate(ref _entry.value, list.Count);
    184.                         for (int i = 0; i < list.Count; ++i)
    185.                         {
    186.                             _builder.AllocateString(ref array[i], list[i]);
    187.                         }
    188.                     }
    189.                 }));
    190.         }
    191.  
    192.         public static void AllocateHashMap<TValue>(ref this BlobBuilder builder,
    193.             ref BlobHashMap<BlobString, TValue> hashMap, Dictionary<string, TValue> value)
    194.             where TValue : struct
    195.         {
    196.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    197.                 ((ref BlobBuilder _builder, ref BlobHashMap<BlobString, TValue>.Entry _entry,
    198.                     in KeyValuePair<string, TValue> _value) =>
    199.                 {
    200.                     _builder.AllocateString(ref _entry.key, _value.Key);
    201.                     _entry.value = _value.Value;
    202.                 }));
    203.         }
    204.      
    205.         public static void AllocateHashMap<TValue>(ref this BlobBuilder builder,
    206.             ref BlobHashMap<BlobString, BlobArray<TValue>> hashMap, Dictionary<string, List<TValue>> value)
    207.             where TValue : struct
    208.         {
    209.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    210.                 ((ref BlobBuilder _builder, ref BlobHashMap<BlobString,BlobArray<TValue>>.Entry _entry,
    211.                     in KeyValuePair<string, List<TValue>> _value) =>
    212.                 {
    213.                     _builder.AllocateString(ref _entry.key, _value.Key);
    214.                  
    215.                     if (_value.Value != null && _value.Value.Count > 0)
    216.                     {
    217.                         _builder.AllocateArray(ref _entry.value, _value.Value);
    218.                     }
    219.                 }));
    220.         }
    221.  
    222.         public static void AllocateHashMap<TKey, TValue, TValueProxy>(ref this BlobBuilder builder,
    223.             ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKey, TValueProxy> value)
    224.             where TKey : struct, IEquatable<TKey>
    225.             where TValue : struct
    226.             where TValueProxy : IBlobProxy<TValue>
    227.         {
    228.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    229.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
    230.                     in KeyValuePair<TKey, TValueProxy> _value) =>
    231.                 {
    232.                     _entry.key = _value.Key;
    233.                     _value.Value.Build(ref _builder, ref _entry.value);
    234.                 }));
    235.         }
    236.  
    237.         public static void AllocateHashMap<TKey, TValue, TValueProxy>(ref this BlobBuilder builder,
    238.             ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKey, List<TValueProxy>> value)
    239.             where TKey : struct, IEquatable<TKey>
    240.             where TValue : struct
    241.             where TValueProxy : IBlobProxy<TValue>
    242.         {
    243.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    244.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
    245.                     in KeyValuePair<TKey, List<TValueProxy>> _value) =>
    246.                 {
    247.                     _entry.key = _value.Key;
    248.  
    249.                     var list = _value.Value;
    250.                     if (list != null && list.Count > 0)
    251.                     {
    252.                         var array = _builder.Allocate(ref _entry.value, list.Count);
    253.                         for (int i = 0; i < list.Count; ++i)
    254.                         {
    255.                             list[i].Build(ref _builder, ref array[i]);
    256.                         }
    257.                     }
    258.                 }));
    259.         }
    260.  
    261.         public static void AllocateHashMap<TKey, TValue, TKeyProxy>(ref this BlobBuilder builder,
    262.             ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKeyProxy, TValue> value)
    263.             where TKey : struct, IEquatable<TKey>
    264.             where TValue : struct
    265.             where TKeyProxy : IBlobProxy<TKey>
    266.         {
    267.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    268.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
    269.                     in KeyValuePair<TKeyProxy, TValue> _value) =>
    270.                 {
    271.                     _value.Key.Build(ref _builder, ref _entry.key);
    272.                     _entry.value = _value.Value;
    273.                 }));
    274.         }
    275.  
    276.         public static void AllocateHashMap<TKey, TValue, TKeyProxy>(ref this BlobBuilder builder,
    277.             ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKeyProxy, List<TValue>> value)
    278.             where TKey : struct, IEquatable<TKey>
    279.             where TValue : struct
    280.             where TKeyProxy : IBlobProxy<TKey>
    281.         {
    282.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    283.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
    284.                     in KeyValuePair<TKeyProxy, List<TValue>> _value) =>
    285.                 {
    286.                     _value.Key.Build(ref _builder, ref _entry.key);
    287.  
    288.                     var list = _value.Value;
    289.                     if (list != null && list.Count > 0)
    290.                     {
    291.                         _builder.AllocateArray(ref _entry.value, list);
    292.                     }
    293.                 }));
    294.         }
    295.  
    296.         public static void AllocateHashMap<TKey, TValue, TKeyProxy, TValueProxy>(ref this BlobBuilder builder,
    297.             ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKeyProxy, TValueProxy> value)
    298.             where TKey : struct, IEquatable<TKey>
    299.             where TValue : struct
    300.             where TKeyProxy : IBlobProxy<TKey>
    301.             where TValueProxy : IBlobProxy<TValue>
    302.         {
    303.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    304.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
    305.                     in KeyValuePair<TKeyProxy, TValueProxy> _value) =>
    306.                 {
    307.                     _value.Key.Build(ref _builder, ref _entry.key);
    308.                     _value.Value.Build(ref _builder, ref _entry.value);
    309.                 }));
    310.         }
    311.      
    312.         public static void AllocateHashMap<TKey, TValue, TKeyProxy, TValueProxy>(ref this BlobBuilder builder,
    313.             ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKeyProxy, List<TValueProxy>> value)
    314.             where TKey : struct, IEquatable<TKey>
    315.             where TValue : struct
    316.             where TKeyProxy : IBlobProxy<TKey>
    317.             where TValueProxy : IBlobProxy<TValue>
    318.         {
    319.             InternalAllocateHashMap(ref builder, ref hashMap, value,
    320.                 ((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
    321.                     in KeyValuePair<TKeyProxy, List<TValueProxy>> _value) =>
    322.                 {
    323.                     _value.Key.Build(ref _builder, ref _entry.key);
    324.                  
    325.                     var list = _value.Value;
    326.                     if (list != null && list.Count > 0)
    327.                     {
    328.                         var array = _builder.Allocate(ref _entry.value, list.Count);
    329.                         for (int i = 0; i < list.Count; ++i)
    330.                         {
    331.                             list[i].Build(ref _builder, ref array[i]);
    332.                         }
    333.                     }
    334.                 }));
    335.         }
    336.      
    337.         public static void ConvertToDictionary<TKey, TValue>(ref this BlobHashMap<TKey, TValue> hashMap,
    338.             out Dictionary<TKey, TValue> result)
    339.             where TKey : struct, IEquatable<TKey> where TValue : struct
    340.         {
    341.             var temp = new Dictionary<TKey, TValue>();
    342.  
    343.             hashMap.ForEach(((ref TKey _key, ref TValue _value) => { temp.Add(_key, _value); }));
    344.  
    345.             result = temp;
    346.         }
    347.  
    348.         public static void ConvertToDictionaryProxyKey<TKey, TValue, TKeyProxy>(
    349.             ref this BlobHashMap<TKey, TValue> hashMap,
    350.             out Dictionary<TKeyProxy, TValue> result)
    351.             where TKey : struct, IEquatable<TKey>
    352.             where TValue : struct
    353.             where TKeyProxy : IBlobAutoProxy<TKey>, new()
    354.         {
    355.             var temp = new Dictionary<TKeyProxy, TValue>();
    356.  
    357.             hashMap.ForEach(((ref TKey _key, ref TValue _value) =>
    358.             {
    359.                 var k = new TKeyProxy();
    360.                 k.Revert(ref _key);
    361.  
    362.                 temp.Add(k, _value);
    363.             }));
    364.  
    365.             result = temp;
    366.         }
    367.  
    368.         public static void ConvertToDictionaryProxyValue<TKey, TValue, TValueProxy>(
    369.             ref this BlobHashMap<TKey, TValue> hashMap,
    370.             out Dictionary<TKey, TValueProxy> result)
    371.             where TKey : struct, IEquatable<TKey>
    372.             where TValue : struct
    373.             where TValueProxy : IBlobAutoProxy<TValue>, new()
    374.         {
    375.             var temp = new Dictionary<TKey, TValueProxy>();
    376.  
    377.             hashMap.ForEach(((ref TKey _key, ref TValue _value) =>
    378.             {
    379.                 var v = new TValueProxy();
    380.                 v.Revert(ref _value);
    381.  
    382.                 temp.Add(_key, v);
    383.             }));
    384.  
    385.             result = temp;
    386.         }
    387.  
    388.         public static void ConvertToDictionaryProxyKeyValue<TKey, TValue, TKeyProxy, TValueProxy>(
    389.             ref this BlobHashMap<TKey, TValue> hashMap,
    390.             out Dictionary<TKeyProxy, TValueProxy> result)
    391.             where TKey : struct, IEquatable<TKey>
    392.             where TValue : struct
    393.             where TKeyProxy : IBlobAutoProxy<TKey>, new()
    394.             where TValueProxy : IBlobAutoProxy<TValue>, new()
    395.         {
    396.             var temp = new Dictionary<TKeyProxy, TValueProxy>();
    397.  
    398.             hashMap.ForEach(((ref TKey _key, ref TValue _value) =>
    399.             {
    400.                 var k = new TKeyProxy();
    401.                 k.Revert(ref _key);
    402.  
    403.                 var v = new TValueProxy();
    404.                 v.Revert(ref _value);
    405.  
    406.                 temp.Add(k, v);
    407.             }));
    408.  
    409.             result = temp;
    410.         }
    411.  
    412.         public static void ConvertToDictionary<TKey>(ref this BlobHashMap<TKey, BlobString> hashMap,
    413.             out Dictionary<TKey, string> result)
    414.             where TKey : struct, IEquatable<TKey>
    415.         {
    416.             var temp = new Dictionary<TKey, string>();
    417.  
    418.             hashMap.ForEach(((ref TKey _key, ref BlobString _value) => { temp.Add(_key, _value.ToString()); }));
    419.  
    420.             result = temp;
    421.         }
    422.  
    423.         public static void ConvertToDictionary<TValue>(ref this BlobHashMap<BlobString, TValue> hashMap,
    424.             out Dictionary<string, TValue> result)
    425.             where TValue : struct
    426.         {
    427.             var temp = new Dictionary<string, TValue>();
    428.  
    429.             hashMap.ForEach(((ref BlobString _key, ref TValue _value) => { temp.Add(_key.ToString(), _value); }));
    430.  
    431.             result = temp;
    432.         }
    433.  
    434.         public static void ConvertToDictionary(ref this BlobHashMap<BlobString, BlobString> hashMap,
    435.             out Dictionary<string, string> result)
    436.         {
    437.             var temp = new Dictionary<string, string>();
    438.  
    439.             hashMap.ForEach(((ref BlobString _key, ref BlobString _value) =>
    440.             {
    441.                 temp.Add(_key.ToString(), _value.ToString());
    442.             }));
    443.  
    444.             result = temp;
    445.         }
    446.     }
    Example :
    Code (CSharp):
    1. var data = new Dictionary<TDataKey, TDataValue>();
    2.  
    3.             for (var i = 0; i < _randomDataCount; ++i)
    4.             {
    5.                 var pair = _randomData();
    6.                 data[pair.Key] = pair.Value;
    7.             }
    8.  
    9.             var builder = new BlobBuilder(Allocator.Temp);
    10.  
    11.             try
    12.             {
    13.                 ref var root = ref builder.ConstructRoot<BlobHashMap<TKey, TValue>>();
    14.                 builder.AllocateHashMap(ref root, data);
    15.                 var result = builder.CreateBlobAssetReference<BlobHashMap<TKey, TValue>>(Allocator.Temp);
    16.  
    17.                 MemoryBinaryReader reader;
    18.                 MemoryBinaryWriter writer;
    19.  
    20.                 unsafe
    21.                 {
    22.                     writer = new MemoryBinaryWriter();
    23.                     writer.Write(result);
    24.                     reader = new MemoryBinaryReader(writer.Data);
    25.                 }
    26.  
    27.                 try
    28.                 {
    29.                     _assertion(reader, data);
    30.                 }
    31.                 finally
    32.                 {
    33.                     writer.Dispose();
    34.                     reader.Dispose();
    35.                 }
    36.             }
    37.             finally
    38.             {
    39.                 builder.Dispose();
    40.             }
     
    burningmime likes this.
  11. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    That looks like a solid implementation in line with what I would expect.
    Creation via NativeHashMap seems useful...
     
  12. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    I got what it means by "It will not work with serialized data". BlobAssetReference<T> can only serialize and deserialize fixed-sized structs. Those extra data after the struct memory location will be lost when serialized.

    I got a further question, for a blob hashmap it needs serial BlobArray<T> for values, keys, nexts, and buckets.
    Question 1. In this case, Can these blob arrays be nested inside of a BlobAssetReference<BlobHashMap>?
    Question 2. Can I assure these BlobArrays are allocated contiguously?

    I think I can still construct a UnsafeHashMapData from separate BlobArray<T> using pointers. And use existing UnsafeHashMapData API to build new ReadonlyHashMap/Set/Multimap
    Code (CSharp):
    1.  
    2.     /// <summary>
    3.     /// Readonly, Key<=>Value 1:1
    4.     /// </summary>
    5.     public struct BlobHashMap<TKey, TValue>
    6.             where TKey : unmanaged, IEquatable<TKey>
    7.             where TValue : unmanaged
    8.     {
    9.         internal UnsafeHashMapData Buffer;//Not using pointer as it is immutable
    10.         internal BlobHashMap(BlobAssetReference<HashMapDataBlob> blob) { Buffer = blob.Value.Buffer; }
    11.  
    12.         public bool IsEmpty { get { unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) { return UnsafeHashMapData.IsEmpty(m_Buffer); } } } }
    13.  
    14.         public int Count()
    15.         {
    16.             if (Buffer.allocatedIndexLength <= 0) return 0;
    17.             unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return UnsafeHashMapData.GetCount(m_Buffer); }
    18.         }
    19.  
    20.  
    21.         public bool TryGetValue(TKey key, out TValue item)
    22.         {
    23.             NativeMultiHashMapIterator<TKey> tempIt;
    24.             unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out item, out tempIt); }
    25.         }
    26.  
    27.         public bool ContainsKey(TKey key)
    28.         {
    29.             unsafe
    30.             {
    31.                 fixed (UnsafeHashMapData* m_Buffer = &Buffer)
    32.                     return UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out var tempValue, out var tempIt);
    33.             }
    34.         }
    35.  
    36.         public TValue this[TKey key]
    37.         {
    38.             get
    39.             {
    40.                 TValue res;
    41.                 TryGetValue(key, out res);
    42.                 return res;
    43.             }
    44.         }
    45.  
    46.         public NativeArray<TKey> GetKeyArray(Allocator allocator)
    47.         {
    48.             unsafe
    49.             {
    50.                 fixed (UnsafeHashMapData* m_Buffer = &Buffer)
    51.                 {
    52.                     var result = new NativeArray<TKey>(UnsafeHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
    53.                     UnsafeHashMapData.GetKeyArray(m_Buffer, result);
    54.                     return result;
    55.                 }
    56.             }
    57.         }
    58.  
    59.         public NativeArray<TValue> GetValueArray(Allocator allocator)
    60.         {
    61.             unsafe
    62.             {
    63.                 fixed (UnsafeHashMapData* m_Buffer = &Buffer)
    64.                 {
    65.                     var result = new NativeArray<TValue>(UnsafeHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
    66.                     UnsafeHashMapData.GetValueArray(m_Buffer, result);
    67.                     return result;
    68.                 }
    69.             }
    70.         }
    71.  
    72.         public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(Allocator allocator)
    73.         {
    74.             unsafe
    75.             {
    76.                 fixed (UnsafeHashMapData* m_Buffer = &Buffer)
    77.                 {
    78.                     var result = new NativeKeyValueArrays<TKey, TValue>(UnsafeHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
    79.                     UnsafeHashMapData.GetKeyValueArrays(m_Buffer, result);
    80.                     return result;
    81.                 }
    82.             }
    83.         }
    84.     }
    85.  
    86.     /// <summary>
    87.     /// Readonly, Key<=>Value 1:n
    88.     /// </summary>
    89.     public struct BlobMultimap<TKey, TValue>
    90.         where TKey : unmanaged, IEquatable<TKey>
    91.         where TValue : unmanaged
    92.     {
    93.         internal UnsafeHashMapData Buffer;
    94.         internal BlobMultimap(BlobAssetReference<HashMapDataBlob> blob) { Buffer = blob.Value.Buffer; }
    95.  
    96.         public bool IsEmpty { get { unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) { return UnsafeHashMapData.IsEmpty(m_Buffer); } } } }
    97.  
    98.         public int Count()
    99.         {
    100.             if (Buffer.allocatedIndexLength <= 0) return 0;
    101.             unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return UnsafeHashMapData.GetCount(m_Buffer); }
    102.         }
    103.  
    104.         public bool TryGetFirstValue(TKey key, out TValue item, out NativeMultiHashMapIterator<TKey> it)
    105.         {
    106.             unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out item, out it); }
    107.         }
    108.  
    109.         public int CountValuesForKey(TKey key)
    110.         {
    111.             if (!TryGetFirstValue(key, out var value, out var iterator))
    112.             {
    113.                 return 0;
    114.             }
    115.  
    116.             var count = 1;
    117.             while (TryGetNextValue(out value, ref iterator))
    118.             {
    119.                 count++;
    120.             }
    121.  
    122.             return count;
    123.         }
    124.  
    125.         public bool TryGetNextValue(out TValue item, ref NativeMultiHashMapIterator<TKey> it)
    126.         { unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return UnsafeHashMapBase<TKey, TValue>.TryGetNextValueAtomic(m_Buffer, out item, ref it); } }
    127.  
    128.         public bool ContainsKey(TKey key)
    129.         { unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(m_Buffer, key, out var tempValue, out var tempIt); } }
    130.  
    131.         public NativeArray<TKey> GetKeyArray(Allocator allocator)
    132.         {
    133.             unsafe
    134.             {
    135.                 fixed (UnsafeHashMapData* m_Buffer = &Buffer)
    136.                 {
    137.                     var result = new NativeArray<TKey>(UnsafeHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
    138.                     UnsafeHashMapData.GetKeyArray(m_Buffer, result);
    139.                     return result;
    140.                 }
    141.             }
    142.         }
    143.  
    144.         public NativeArray<TValue> GetValueArray(Allocator allocator)
    145.         {
    146.             unsafe
    147.             {
    148.                 fixed (UnsafeHashMapData* m_Buffer = &Buffer)
    149.                 {
    150.                     var result = new NativeArray<TValue>(UnsafeHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
    151.                     UnsafeHashMapData.GetValueArray(m_Buffer, result);
    152.                     return result;
    153.                 }
    154.             }
    155.         }
    156.  
    157.         public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(Allocator allocator)
    158.         {
    159.             unsafe
    160.             {
    161.                 fixed (UnsafeHashMapData* m_Buffer = &Buffer)
    162.                 {
    163.                     var result = new NativeKeyValueArrays<TKey, TValue>(UnsafeHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory);
    164.                     UnsafeHashMapData.GetKeyValueArrays(m_Buffer, result);
    165.                     return result;
    166.                 }
    167.             }
    168.         }
    169.     }
    170.  
    171.     /// <summary>
    172.     /// Readonly, Element is unique
    173.     /// </summary>
    174.     public unsafe struct BlobHashSet<T>
    175.         where T : unmanaged, IEquatable<T>
    176.     {
    177.         internal UnsafeHashMapData Buffer;
    178.         internal BlobHashSet(BlobAssetReference<HashMapDataBlob> blob) { Buffer = blob.Value.Buffer; }
    179.  
    180.         public bool IsEmpty { get { unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) { return UnsafeHashMapData.IsEmpty(m_Buffer); } } } }
    181.  
    182.         public int Count()
    183.         {
    184.             if (Buffer.allocatedIndexLength <= 0) return 0;
    185.             unsafe { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return UnsafeHashMapData.GetCount(m_Buffer); }
    186.         }
    187.  
    188.         public bool Contains(T i
     
    bb8_1 likes this.
  13. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    I got this BlobArray version working.
    Still. you need to has internal access to Unity.Collections; Please refer to this:
    https://forum.unity.com/threads/please-help-bug-in-unity-csproj-generation.868198/#post-5715838

    BlobArray<T> version Blob HashMap
    Code (CSharp):
    1.  
    2.     unsafe public struct HashMapDataBlob<TKey, TValue>
    3.             where TKey : unmanaged, IEquatable<TKey>
    4.             where TValue : unmanaged
    5.     {
    6.         internal int m_count;
    7.         internal int m_bucketCapacityMask;
    8.  
    9.         public BlobArray<TKey> keys;
    10.         public BlobArray<TValue> values;
    11.         public BlobArray<int> buckets;
    12.         public BlobArray<int> next;
    13.  
    14.         unsafe internal static BlobAssetReference<HashMapDataBlob<TKey, TValue>> FromHashMapData(UnsafeHashMapData* data, Allocator allocator = Allocator.Persistent)
    15.         {
    16.             UnsafeHashMapData.IsBlittableAndThrow<TKey, TValue>();
    17.             var pairCount = UnsafeHashMapData.GetCount(data);
    18.             var bucketCapacityMask = data->bucketCapacityMask;
    19.             var bucketCapacity = bucketCapacityMask + 1;
    20.             var builder = new BlobBuilder(Allocator.Temp);
    21.             ref var root = ref builder.ConstructRoot<HashMapDataBlob<TKey, TValue>>();
    22.             root.m_count = pairCount;
    23.             root.m_bucketCapacityMask = bucketCapacityMask;
    24.             var valueBuilder = builder.Allocate(ref root.values, pairCount);
    25.             var keyBuilder = builder.Allocate(ref root.keys, pairCount);
    26.             var nextBuilder = builder.Allocate(ref root.next, pairCount);
    27.             var bucketBuilder = builder.Allocate(ref root.buckets, bucketCapacity);
    28.             UnsafeUtility.MemSet(nextBuilder.GetUnsafePtr(), 0xff, pairCount * sizeof(int));
    29.             UnsafeUtility.MemSet(bucketBuilder.GetUnsafePtr(), 0xff, bucketCapacity * sizeof(int));
    30.  
    31.             int* bucketsIn = (int*)data->buckets;
    32.             int* nextsIn = (int*)data->next;
    33.             TKey* keysIn = (TKey*)data->keys;
    34.             TValue* valueIn = (TValue*)data->values;
    35.             //Rebuild HashMap,
    36.             //1. remove all gaps
    37.             //2. order by buckets index(hash)
    38.             for (int bktId = 0, pairId = 0; bktId < bucketCapacity; bktId++)
    39.             {
    40.                 var entry = bucketsIn[bktId];
    41.                 if (entry >= 0)
    42.                 {
    43.                     Assert.IsTrue((entry < (data->keyCapacity)));
    44.                     Assert.IsTrue((pairId < pairCount));
    45.                     bucketBuilder[bktId] = pairId;
    46.                     keyBuilder[pairId] = keysIn[entry];
    47.                     valueBuilder[pairId] = valueIn[entry];
    48.                     pairId++;
    49.                     entry = nextsIn[entry];
    50.                     while (entry >= 0)
    51.                     {
    52.                         Assert.IsTrue((entry < (data->keyCapacity)));
    53.                         Assert.IsTrue((pairId < pairCount));
    54.                         keyBuilder[pairId] = keysIn[entry];
    55.                         valueBuilder[pairId] = valueIn[entry];
    56.                         nextBuilder[pairId - 1] = pairId;
    57.                         pairId++;
    58.                         entry = nextsIn[entry];
    59.                     }
    60.                 }
    61.             }
    62.             return builder.CreateBlobAssetReference<HashMapDataBlob<TKey, TValue>>(allocator);
    63.         }
    64.  
    65.         unsafe internal UnsafeHashMapData Buffer
    66.         {
    67.             get
    68.             {
    69.                 var buffer = s_Neg1;
    70.                 buffer.values = (byte*)values.GetUnsafePtr();
    71.                 buffer.keys = (byte*)keys.GetUnsafePtr();
    72.                 buffer.next = (byte*)next.GetUnsafePtr();
    73.                 buffer.buckets = (byte*)buckets.GetUnsafePtr();
    74.                 buffer.keyCapacity = m_count;
    75.                 buffer.bucketCapacityMask = m_bucketCapacityMask;
    76.                 buffer.allocatedIndexLength = m_count;
    77.                 return buffer;
    78.             }
    79.         }
    80.  
    81.         static readonly UnsafeHashMapData s_Neg1 = CreateNeg1();
    82.         unsafe static UnsafeHashMapData CreateNeg1()
    83.         {
    84.             var data = new UnsafeHashMapData();
    85.             UnsafeUtility.MemSet(&data, 0xff, UnsafeUtility.SizeOf<UnsafeHashMapData>());
    86.             return data;
    87.         }
    88.     }

    Conversion extension
    Code (CSharp):
    1.    unsafe public static class HashMapDataBlobExt
    2.     {
    3.         unsafe public static BlobAssetReference<HashMapDataBlob<T, bool>> CreateBlobAsset<T>(this UnsafeHashSet<T> set, Allocator allocator = Allocator.Persistent)
    4.             where T : unmanaged, IEquatable<T>
    5.             => HashMapDataBlob<T, bool>.FromHashMapData(set.m_Data.m_Buffer, allocator);
    6.  
    7.         unsafe public static BlobAssetReference<HashMapDataBlob<T, bool>> ConvertToBlob<T>(this NativeHashSet<T> set, Allocator allocator = Allocator.Persistent)
    8.             where T : unmanaged, IEquatable<T>
    9.             => HashMapDataBlob<T, bool>.FromHashMapData(set.m_Data.m_HashMapData.m_Buffer, allocator);
    10.  
    11.         unsafe public static BlobAssetReference<HashMapDataBlob<TKey, TValue>> CreateBlobAsset<TKey, TValue>(this UnsafeHashMap<TKey, TValue> map, Allocator allocator = Allocator.Persistent)
    12.             where TKey : unmanaged, IEquatable<TKey>
    13.             where TValue : unmanaged
    14.             => HashMapDataBlob<TKey, TValue>.FromHashMapData(map.m_Buffer, allocator);
    15.  
    16.         unsafe public static BlobAssetReference<HashMapDataBlob<TKey, TValue>> CreateBlobAsset<TKey, TValue>(this NativeHashMap<TKey, TValue> map, Allocator allocator = Allocator.Persistent)
    17.             where TKey : unmanaged, IEquatable<TKey>
    18.             where TValue : unmanaged
    19.             => HashMapDataBlob<TKey, TValue>.FromHashMapData(map.m_HashMapData.m_Buffer, allocator);
    20.  
    21.         unsafe public static BlobAssetReference<HashMapDataBlob<TKey, TValue>> CreateBlobAsset<TKey, TValue>(this UnsafeMultiHashMap<TKey, TValue> map, Allocator allocator = Allocator.Persistent)
    22.             where TKey : unmanaged, IEquatable<TKey>
    23.             where TValue : unmanaged
    24.             => HashMapDataBlob<TKey, TValue>.FromHashMapData(map.m_Buffer, allocator);
    25.  
    26.         unsafe public static BlobAssetReference<HashMapDataBlob<TKey, TValue>> CreateBlobAsset<TKey, TValue>(this NativeMultiHashMap<TKey, TValue> map, Allocator allocator = Allocator.Persistent)
    27.             where TKey : unmanaged, IEquatable<TKey>
    28.             where TValue : unmanaged
    29.             => HashMapDataBlob<TKey, TValue>.FromHashMapData(map.m_MultiHashMapData.m_Buffer, allocator);
    30.  
    31.         public static BlobHashSet<T> AsReadonlyHashSet<T>(this BlobAssetReference<HashMapDataBlob<T, bool>> blob) where T : unmanaged, IEquatable<T>
    32.             => new BlobHashSet<T>(blob);
    33.  
    34.         public static BlobHashMap<TKey, TValue> AsReadonlyHashMap<TKey, TValue>(this BlobAssetReference<HashMapDataBlob<TKey, TValue>> blob)
    35.             where TKey : unmanaged, IEquatable<TKey>
    36.             where TValue : unmanaged
    37.          => new BlobHashMap<TKey, TValue>(blob);
    38.  
    39.         public static BlobMultimap<TKey, TValue> AsReadonlyMultiMap<TKey, TValue>(this BlobAssetReference<HashMapDataBlob<TKey, TValue>> blob)
    40.             where TKey : unmanaged, IEquatable<TKey>
    41.             where TValue : unmanaged => new BlobMultimap<TKey, TValue>(blob);
    42.     }
    And Readonly accessors
    Code (CSharp):
    1.  
    2.     /// <summary>
    3.     /// Readonly, Key<=>Value 1:1
    4.     /// </summary>
    5.     public struct BlobHashMap<TKey, TValue>
    6.             where TKey : unmanaged, IEquatable<TKey>
    7.             where TValue : unmanaged
    8.     {
    9.         internal UnsafeHashMapData Buffer;
    10.         internal BlobHashMap(BlobAssetReference<HashMapDataBlob<TKey, TValue>> blob) { Buffer = blob.Value.Buffer; }
    11.         internal unsafe UnsafeHashMapData* pBuffer { get { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return m_Buffer; } }
    12.  
    13.         public bool IsEmpty => Buffer.allocatedIndexLength <= 0;
    14.         //allocatedIndexLength is valid Because data was rebuilt odered and compact
    15.         public int Count => Buffer.allocatedIndexLength;
    16.  
    17.         unsafe public bool TryGetValue(TKey key, out TValue item)
    18.             => UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(pBuffer, key, out item, out _);
    19.  
    20.         unsafe public bool ContainsKey(TKey key)
    21.              => UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(pBuffer, key, out _, out _);
    22.  
    23.         public TValue this[TKey key] { get { TryGetValue(key, out var res); return res; } }
    24.  
    25.         public NativeArray<TKey> GetKeyArray(Allocator allocator)
    26.         {
    27.             var result = new NativeArray<TKey>(Buffer.allocatedIndexLength, allocator, NativeArrayOptions.UninitializedMemory);
    28.             unsafe { UnsafeUtility.MemCpy(result.GetUnsafePtr(), Buffer.keys, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TKey>()); }
    29.             return result;
    30.         }
    31.  
    32.         public NativeArray<TValue> GetValueArray(Allocator allocator)
    33.         {
    34.             var result = new NativeArray<TValue>(Buffer.allocatedIndexLength, allocator, NativeArrayOptions.UninitializedMemory);
    35.             unsafe { UnsafeUtility.MemCpy(result.GetUnsafePtr(), Buffer.values, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TValue>()); }
    36.             return result;
    37.         }
    38.  
    39.         public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(Allocator allocator)
    40.         {
    41.             var result = new NativeKeyValueArrays<TKey, TValue>(Buffer.allocatedIndexLength, allocator, NativeArrayOptions.UninitializedMemory);
    42.             unsafe
    43.             {
    44.                 UnsafeUtility.MemCpy(result.Keys.GetUnsafePtr(), Buffer.keys, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TKey>());
    45.                 UnsafeUtility.MemCpy(result.Values.GetUnsafePtr(), Buffer.values, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TValue>());
    46.             }
    47.             return result;
    48.         }
    49.     }
    50.  
    51.     /// <summary>
    52.     /// Readonly, Key<=>Value 1:n
    53.     /// </summary>
    54.     public struct BlobMultimap<TKey, TValue>
    55.         where TKey : unmanaged, IEquatable<TKey>
    56.         where TValue : unmanaged
    57.     {
    58.         internal UnsafeHashMapData Buffer;
    59.         internal BlobMultimap(BlobAssetReference<HashMapDataBlob<TKey, TValue>> blob) { Buffer = blob.Value.Buffer; }
    60.         internal unsafe UnsafeHashMapData* pBuffer { get { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return m_Buffer; } }
    61.  
    62.         public bool IsEmpty => Buffer.allocatedIndexLength <= 0;
    63.         //allocatedIndexLength is valid Because data was rebuilt odered and compact
    64.         public int Count => Buffer.allocatedIndexLength;
    65.  
    66.         unsafe public int CountValuesForKey(TKey key)
    67.         {
    68.             //this is valid Because data was rebuilt odered and compact
    69.             int* buckets = (int*)Buffer.buckets;
    70.             int bucketId = key.GetHashCode() & Buffer.bucketCapacityMask;
    71.             var start = buckets[bucketId];
    72.             var end = bucketId >= Buffer.bucketCapacityMask ? Buffer.allocatedIndexLength : buckets[bucketId + 1];
    73.             return end - start;
    74.         }
    75.  
    76.         unsafe public bool TryGetFirstValue(TKey key, out TValue item, out NativeMultiHashMapIterator<TKey> it)
    77.             => UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(pBuffer, key, out item, out it);
    78.  
    79.         unsafe public bool TryGetNextValue(out TValue item, ref NativeMultiHashMapIterator<TKey> it)
    80.             => UnsafeHashMapBase<TKey, TValue>.TryGetNextValueAtomic(pBuffer, out item, ref it);
    81.  
    82.         unsafe public bool ContainsKey(TKey key) => UnsafeHashMapBase<TKey, TValue>.TryGetFirstValueAtomic(pBuffer, key, out _, out _);
    83.  
    84.         public NativeArray<TKey> GetKeyArray(Allocator allocator)
    85.         {
    86.             var result = new NativeArray<TKey>(Buffer.allocatedIndexLength, allocator, NativeArrayOptions.UninitializedMemory);
    87.             unsafe { UnsafeUtility.MemCpy(result.GetUnsafePtr(), Buffer.keys, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TKey>()); }
    88.             return result;
    89.         }
    90.  
    91.         public NativeArray<TValue> GetValueArray(Allocator allocator)
    92.         {
    93.             var result = new NativeArray<TValue>(Buffer.allocatedIndexLength, allocator, NativeArrayOptions.UninitializedMemory);
    94.             unsafe { UnsafeUtility.MemCpy(result.GetUnsafePtr(), Buffer.values, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TValue>()); }
    95.             return result;
    96.         }
    97.  
    98.         public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(Allocator allocator)
    99.         {
    100.             var result = new NativeKeyValueArrays<TKey, TValue>(Buffer.allocatedIndexLength, allocator, NativeArrayOptions.UninitializedMemory);
    101.             unsafe
    102.             {
    103.                 UnsafeUtility.MemCpy(result.Keys.GetUnsafePtr(), Buffer.keys, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TKey>());
    104.                 UnsafeUtility.MemCpy(result.Values.GetUnsafePtr(), Buffer.values, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<TValue>());
    105.             }
    106.             return result;
    107.         }
    108.     }
    109.  
    110.     /// <summary>
    111.     /// Readonly, Element is unique
    112.     /// </summary>
    113.     public unsafe struct BlobHashSet<T>
    114.         where T : unmanaged, IEquatable<T>
    115.     {
    116.         internal UnsafeHashMapData Buffer;
    117.         internal BlobHashSet(BlobAssetReference<HashMapDataBlob<T, bool>> blob) { Buffer = blob.Value.Buffer; }
    118.         internal unsafe UnsafeHashMapData* pBuffer { get { fixed (UnsafeHashMapData* m_Buffer = &Buffer) return m_Buffer; } }
    119.  
    120.         public bool IsEmpty => Buffer.allocatedIndexLength <= 0;
    121.         //allocatedIndexLength is valid Because data was rebuilt odered and compact
    122.         public int Count => Buffer.allocatedIndexLength;
    123.  
    124.         unsafe public bool Contains(T item) => UnsafeHashMapBase<T, bool>.TryGetFirstValueAtomic(pBuffer, item, out _, out _);
    125.  
    126.         public NativeArray<T> ToNativeArray(Allocator allocator)
    127.         {
    128.             var result = new NativeArray<T>(Buffer.allocatedIndexLength, allocator, NativeArrayOptions.UninitializedMemory);
    129.             unsafe { UnsafeUtility.MemCpy(result.GetUnsafePtr(), Buffer.keys, Buffer.allocatedIndexLength * UnsafeUtility.SizeOf<T>()); }
    130.             return result;
    131.         }
    132.     }
     
    Last edited: Sep 8, 2020
    bb8_1 and Egad_McDad like this.
  14. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Yes. BlobArray & BlobPtr can be nested inside each other.
     
  15. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    This is also a correct way of implementing it.

    The question is if the copying into the struct will result in a performance loss compared to having methods that work against Blob Array etc directly similar to what Thermos did.

    I am not sure. It is possible burst will optimise it away or not. It is worth making a performance test for and comparing TryGetValue performance of both approaches. Overall the implementation that Thermos posted is quite clean I think.

    One extra thing to note, the NativeHashMap implementation are optimised for parallel add / remove etc.
    If the constraint is that building the hashtable can be slow and is done in batch. And only trygetvalue performance matters other hashtable implementations may be faster / better choices.

    Once everything is known upfront you could for example make a hashtable that gurantees that the hash points to the key directly and no probing is necessary. eg. could directly do the lookup and not have any code following the probe. This could make lookup much faster & build time slower. Eg. change a hash modulator / key size until it fits the requirement.


    @Thermos:

    This line in your hashtable is very likely killing perf.
    var bucketIndex = buckets[_key.GetHashCode() % halfPV + halfPV];

    In our unsafehashmap we used & operator to mask the hash code. % operator is generally much more expensive than * + operators on ints across most platforms by often a magnitude of latency difference.

    I think putting some work into precalculating an & mask value + offset, would probably get you significant wins. I assume that is part why you have to have a specialized innerloop for small amounts of keys. It would be worth checking if that can be made unnecessary simply optimising the lookup.
     
  16. Thermos

    Thermos

    Joined:
    Feb 23, 2015
    Posts:
    148
    Thanks for the tips. Actually..I only use this BlobHashMap and outdated blob asset code(Which I copied from ECS 0.3) in our old project, which only tries to solve dictionary serialization and get a faster load time in Monobehavior & ScriptableObject world. Like the screenshot below, editing dictionary data and store into blob data(byte[]).

    1.PNG

    In our new DOTS based project we are working on a new data editing tool, it will map all keys into indices when build the final blob data, so we only need BlobArray, that also gives us a much faster access by using array & index.

    But still, would like to see an official BlobHashMap support.
     
    Last edited: Sep 8, 2020
    mailey99 likes this.
  17. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    If you're going to go to that level, there's Fibonacci hashing, which helps prevent collisions as well as being fast to calculate: https://probablydance.com/2018/06/1...ot-or-a-better-alternative-to-integer-modulo/ .
     
    Lieene-Guo likes this.
  18. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
  19. ShadLady

    ShadLady

    Joined:
    Mar 19, 2019
    Posts:
    8
    Thanks for sharing! There's some missing stuff in your code, could you share the full relative code? I really need a BlobHashMap.:)
     
  20. Thermos

    Thermos

    Joined:
    Feb 23, 2015
    Posts:
    148
    Sure. If you are using older version of unity.collection, you need to replace all UnsafeUtility.AsRef<T> back to UnsafeUtilityEx.AsRef<T>.

    Also, if your project is ECS based, you could replace BlobControl.cs with latest blob asset code in ECS package.
     

    Attached Files:

    bb8_1, Timboc and ShadLady like this.
  21. ShadLady

    ShadLady

    Joined:
    Mar 19, 2019
    Posts:
    8
    That's really a great help! Thanks again!;)
     
  22. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    @Thermos Thanks for sharing. I hope I'll be able to use this to cache some info for my ISystemBase.

    @Joachim_Ante Is there any plan to integrate this (or a similar structure) in the Unity package ?
     
    Timboc likes this.
  23. bartofzo

    bartofzo

    Joined:
    Mar 16, 2017
    Posts:
    151
    I wrote a BlobHashMap and BlobMultiHashMap last week, based on NativeHashMap and NativeMultiHashMap implementation. Doesn't use any unsafe code and should be quite performant!

    https://github.com/bartofzo/BlobHashMaps
     
    Tony_Max, Sarkahn, Kmsxkuse and 3 others like this.
  24. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Can you add a licence to your project ?
     
  25. bartofzo

    bartofzo

    Joined:
    Mar 16, 2017
    Posts:
    151
    I've added an MIT license to it, so feel free to use this code :)
     
    lclemens, WAYNGames and Kmsxkuse like this.
  26. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Thanks,

    It does help to have additional exemples.
    I may not end up using it because I don't like having code I don't fully understand in my projects but it pushed me to learn how map works underneath.
    The best way to understand something is to do it so I'm doing my own implementation focused on read performance because I think it's the aim of the blob structures.
    I'll keep you posted once I have a working prototype (if I get there :p)
     
  27. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Quick question,
    If want to have a BlobArray in a BlobArray, can I make a BlobArray<BlobArray> ? or do I need to make a BlobArray<BlobAssetRefrence<BlobArray>> ?

    EDIT
    : Nop my bad we don't need to have intermediate BlobAssetRefrence, I had a bad if that did not populate the array values...
     
    Last edited: Jan 29, 2021
  28. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Ok, so I came up with my own implementation which seems to works.

    I took the literal approach of having an array of bucket. each bucket has an array of key, values and keyIndex.

    The focus of blob asset being the read performance, I sacrificed performance in the creation of the asset to make sure my buckets are ordered by keyHash and key.

    That way I can make sure that all values for 1 key will be nicely stored one after another.
    The key index is populated with the keyHash, index of the first value for that key in hte values array and number of element for that key.

    Then to get a value, I compute the bucketIndex to find in which bucket the data is. If that bucket contains more than one key, I perform a binary search on the key hash to find the index I need and the for the index I know the index of the first element and the number of element in the values array.

    Knowing the first element position and number of elements, I should be able to have a native array just by copying memory or even better having a read only 'virtual' native array that point to the memory location of the element in the blob's values array.

    I'm bad with this kind of memory manipulation so any help would be great.
    Here is what I do for now and what I tried unsuccessfully (commented) :

    Code (CSharp):
    1. public NativeArray<TValue> GetValuesForKey(TKey key, Allocator allocator = Allocator.Temp)
    2.     {
    3.         int keyIndex = FindKeyIndex(key);
    4.         if (keyIndex < 0)
    5.         {
    6.             return new NativeArray<TValue>(0, allocator);
    7.         }
    8.         int elementCount = KeyIndexes[keyIndex].ElementCount;
    9.  
    10.         NativeArray<TValue> result = new NativeArray<TValue>(elementCount, allocator);
    11.         int firstIndex = KeyIndexes[keyIndex].FirstIndex;
    12.         for (int i = 0; i < elementCount; i++)
    13.         {
    14.             result[i] = ValuesArray[firstIndex + i];
    15.         }
    16.  
    17.         return result;
    18.         /* can't manage to make this memory access work.
    19.          * the idea would be to have a reader array point directly at the memory allocation of the blob asset.
    20.        unsafe
    21.        {
    22.            void* firstElementPointer = UnsafeUtility.AddressOf(ref ValuesArray[KeyIndexes[keyIndex].FirstIndex]);
    23.            return NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<TValue>(firstElementPointer, elementCount * UnsafeUtility.SizeOf<TValue>(), Allocator.None);
    24.        }*/
    25.     }
    Full code is available here : https://github.com/WAYN-Games/Blob

    You can build the blobmap with a dictionnary or native(multi)hashmap. Or you can use the fluent builder.

    If anyone spot any misconception or improvements feel free to share ;).
     
    Last edited: Jun 11, 2021
  29. bartofzo

    bartofzo

    Joined:
    Mar 16, 2017
    Posts:
    151
    I am by no means any expert on hashmap implementations but I wonder if the extra work doing the binary search is worth it. I guess if you have a lot of hash collisions it could be faster, but you still have to do the linear search for keys that are the same. Did you test the performance?

    I don't know about the NativeArray pointer, but you could make an iterator struct and a TryGetFirst and TryGetNext method to avoid using a NativeArray. That's how I did it in my implementation and also how Unity does it.
     
  30. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    My understanding is that in most case I should end up with one key per bucket.
    The reason for that is that I should always have more bucket than keys so if the hash function is well distributed enough it should be unlikely to have more than one key in a bucket. that's why I check the bucket key array size to make before any binary search. Si I should almost always end up with a O(1) lookup complexity.

    If I'm unlucky, I could have more than one key in a bucket. In that case the binary search grant me a O(log(n)) lookup complexity.

    And if I'm really unlucky, I could end up with a hash key collision that mean the key aren’t equal but have the same hash so are in the same bucket. And the binary search could find the hash of the wrong key. If that happen I have no other choice but to perform a linear search on the key equality. this will be done from the colliding index so after the O(log(n)) binary search.

    This will result in a O(log(n)) + O(m) search were n is the number of key in the bucket and m the number of key with the same hash.

    I have not yet done performance test but the 'unit' test runs pretty fast even in the worst case scenario or colliding keys for 1000 values.
     
  31. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
  32. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    So, I planned on using the performance package to get some perf number but it turn out you can't use ref variable in an anonymous lambda so exit the Measure method....

    I made a test scene that on start up generate a nativemultihashmap with 250K values in it.
    I convert that same map into a blobmultihashmap.
    Then I have 2 systems one that operate on the map and the other on the blob.
    Each system does the sum of values % int.maxValue.

    Both system are setup in the same way (map and blob are set at conversion time and store as private system mebers)
    The only difference is that the map uses and iterator and the blob uses of for loop on the array.

    Here is the seult for the map timming in hte profiler :
    upload_2021-1-31_20-53-11.png

    and here is the result for the blob :
    upload_2021-1-31_20-52-51.png

    We end up with a alsmost constant 4 times more performant for the blob.

    I'll try to work on a bursted verion of the blob system to see if it brings even more performance.
    The map version can't be bursted has it would make the ISystemBase non blitable.
     
    Last edited: Jan 31, 2021
  33. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Well I guess there is no denying the performance benefits of using Burst....

    upload_2021-2-1_16-5-2.png


    So to sum up :
    upload_2021-2-1_16-9-20.png

    Note that with the blob I can't go above ~272K elements. I just end up with that exception for the blob while the native map works :

     
  34. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    I created bug repport 1312031 for the InvalidAssetReference error.

    My code works just fine in the unit test with 1 million values added to the map but doesn't work in playmode...

    EDIT :Never mind everything is fine...I was allocating the blobassetref with a Temp allocator... :oops:
     
    Last edited: Feb 5, 2021
    Nyanpas likes this.
  35. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    987
    I'm looking for a hashmap as a blob asset. I tried adopting this code. Cleaned it a bit to my liking:
    Code (CSharp):
    1. public struct BlobHashMap<K, V>
    2.     where K : unmanaged, IEquatable<K>
    3.     where V : unmanaged {
    4.     [StructLayout(LayoutKind.Sequential)]
    5.     public struct Entry {
    6.         public K key;
    7.         public V value;
    8.         public int next;
    9.     }
    10.  
    11.     internal BlobArray<int> buckets;
    12.     internal BlobArray<Entry> data;
    13.     internal int halfPv;
    14.  
    15.     public int Count {
    16.         get {
    17.             return this.data.Length;
    18.         }
    19.     }
    20.  
    21.     public ref V Get(in K key) {
    22.         if (this.buckets.Length == -1) {
    23.             throw new Exception();
    24.         }
    25.  
    26.         if (this.halfPv <= 0 || this.Count <= 5) {
    27.             for (int i = 0, c = this.Count; i < c; ++i) {
    28.                 ref Entry entry = ref this.data[i];
    29.  
    30.                 if (entry.key.Equals(key)) {
    31.                     return ref entry.value;
    32.                 }
    33.             }
    34.         } else {
    35.             int bucketIndex = key.GetHashCode() % this.halfPv;
    36.  
    37.             bucketIndex = this.buckets[bucketIndex + this.halfPv];
    38.  
    39.             while (bucketIndex != -1) {
    40.                 ref Entry entry = ref this.data[bucketIndex];
    41.                 ref K entryKey = ref entry.key;
    42.                 if (entryKey.Equals(key)) {
    43.                     return ref entry.value;
    44.                 }
    45.  
    46.                 bucketIndex = entry.next;
    47.             }
    48.         }
    49.  
    50.         throw new KeyNotFoundException();
    51.     }
    52.  
    53.     public bool ContainsKey(in K key) {
    54.         if (this.halfPv <= 0 || this.Count <= 5) {
    55.             for (int i = 0, c = this.Count; i < c; ++i) {
    56.                 ref Entry entry = ref this.data[i];
    57.  
    58.                 if (entry.key.Equals(key)) {
    59.                     return true;
    60.                 }
    61.             }
    62.         } else {
    63.             int bucketIndex = key.GetHashCode() % this.halfPv;
    64.  
    65.             bucketIndex = this.buckets[bucketIndex + this.halfPv];
    66.  
    67.             while (bucketIndex != -1) {
    68.                 ref Entry entry = ref this.data[bucketIndex];
    69.  
    70.                 if (entry.key.Equals(key)) {
    71.                     return true;
    72.                 }
    73.  
    74.                 bucketIndex = entry.next;
    75.             }
    76.         }
    77.  
    78.         return false;
    79.     }
    80.  
    81.     public bool TryGetValue(in K key, out V value) {
    82.         if (this.halfPv <= 0 || this.Count <= 5) {
    83.             for (int i = 0, c = this.Count; i < c; ++i) {
    84.                 ref Entry entry = ref this.data[i];
    85.  
    86.                 if (entry.key.Equals(key)) {
    87.                     value = entry.value;
    88.  
    89.                     return true;
    90.                 }
    91.             }
    92.         } else {
    93.             int bucketIndex = this.buckets[key.GetHashCode() % this.halfPv + this.halfPv];
    94.  
    95.             while (bucketIndex != -1) {
    96.                 ref Entry entry = ref this.data[bucketIndex];
    97.  
    98.                 if (entry.key.Equals(key)) {
    99.                     value = entry.value;
    100.  
    101.                     return true;
    102.                 }
    103.  
    104.                 bucketIndex = entry.next;
    105.             }
    106.         }
    107.  
    108.         value = default;
    109.  
    110.         return false;
    111.     }
    112.  
    113.     public NativeArray<K> GetKeys(Allocator allocator) {
    114.         int length = this.data.Length;
    115.  
    116.         NativeArray<K> array = new NativeArray<K>(length, allocator, NativeArrayOptions.UninitializedMemory);
    117.         for (int i = 0; i < length; ++i) {
    118.             ref Entry entry = ref this.data[i];
    119.             array[i] = entry.key;
    120.         }
    121.  
    122.         return array;
    123.     }
    124. }
    And here's the test:
    Code (CSharp):
    1. [Test]
    2. public void BasicUsage() {
    3.     Dictionary<int, int2> dictionary = new Dictionary<int, int2>() {
    4.         {
    5.             1, new int2(1, 1)
    6.         },
    7.         {
    8.             2, new int2(2, 2)
    9.         },
    10.         {
    11.             3, new int2(3, 3)
    12.         }
    13.     };
    14.    
    15.     BlobBuilder builder = new BlobBuilder(Allocator.TempJob);
    16.     ref BlobHashMap<int, int2> blobMap = ref builder.ConstructRoot<BlobHashMap<int, int2>>();
    17.     builder.AllocateHashMap(ref blobMap, dictionary);
    18.     BlobAssetReference<BlobHashMap<int, int2>> reference = builder.CreateBlobAssetReference<BlobHashMap<int, int2>>(Allocator.Temp);
    19.     builder.Dispose();
    20.    
    21.     Assert.IsTrue(reference.Value.Get(1).Equals(new int2(1, 1)));
    22.     Assert.IsTrue(reference.Value.Get(2).Equals(new int2(2, 2)));
    23.     Assert.IsTrue(reference.Value.Get(3).Equals(new int2(3, 3)));
    24. }
    However, I'm getting this compiler error:
    F:\Projects\Yawgmoth\YawgmothUnitySource\Assets\Game\Editor\Tests\BlobHashMapTest.cs(32,13): error Assets\Game\Editor\Tests\BlobHashMapTest.cs(32,13): error ConstructBlobWithRefTypeViolation: You may not build a type BlobHashMap`2 with Construct as BlobHashMap`2.data[].key is a reference or pointer. Only non-reference types are allowed in Blobs.

    I'm stumped. I already set generic K as unmanaged.
     
  36. Thermos

    Thermos

    Joined:
    Feb 23, 2015
    Posts:
    148
    I think this is a bug of ECS 0.17 which they add some wrong safety checks, I tried @bartofzo 's solution, same happened. In the meantime, you might have to use the non-generic version, something like BlobHashMapIntInt...
     
  37. bartofzo

    bartofzo

    Joined:
    Mar 16, 2017
    Posts:
    151
    Yep this is indeed a bug. The code was working fine before 0.17. Hopefully they'll fix it soon. I think a temporary fix is mentioned in this thread:

    https://forum.unity.com/threads/entities-0-17-changelog.1020202/page-3#post-6791726
     
    davenirline likes this.
  38. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    353
  39. flagredomega

    flagredomega

    Joined:
    Oct 12, 2017
    Posts:
    12
    No
     
  40. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    What is IBlobAutoProxy?
     
  41. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    353
    Have unity devs any plans on providing official support for blob hash collections?
     
    lclemens and yokobe0012 like this.
  42. highcore

    highcore

    Joined:
    Jun 4, 2021
    Posts:
    4
    Use solution from GitHub for BlobHashMap... really weird why this why such an obvious and necessary data structure is not supported
     
    lclemens likes this.
  43. zonunity

    zonunity

    Joined:
    Oct 19, 2022
    Posts:
    2
    This is a good idea. I set IComponentData to an int and Entity, and then converted it to NativeHashMap in the system.