Search Unity

NativeHashMap - TryReplaceValue

Discussion in 'Entity Component System' started by eizenhorn, Feb 14, 2019.

  1. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    May be useful for someone, extended NativeHashMap container for replacing value by key.
    Cause native Unity implementation not support that yet ^_^
    upload_2019-2-14_18-39-17.png

    May be someone posted this before on forum (really don't remember, cause so many posts watched by me on forum from the beginning of ECS, and I can't remember all :) if so, please link thread with that )

    Implemented 2 variations - by indexer and by method call:
    Indexer
    Code (CSharp):
    1. public TValue this [TKey key]
    2.         {
    3.             get
    4.             {
    5.                 TValue res;
    6. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    7.                 if (TryGetValue(key, out res))
    8.                     return res;
    9.                 else
    10.                     throw new ArgumentException($"Key: {key} is not present in the NativeHashMap.");
    11. #else
    12.                 TryGetValue(key, out res);
    13.                 return res;
    14. #endif
    15.             }
    16.             set
    17.             {
    18. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    19.                 if (!TryReplaceValue(key, value))
    20.                     throw new ArgumentException($"Key: {key} is not present in the NativeHashMap.");
    21. #else
    22.                 TryReplaceValue(key, value)
    23. #endif
    24.             }
    25.         }
    TryReplaceValue
    Code (CSharp):
    1. public bool TryReplaceValue(TKey key, TValue item)
    2.         {
    3. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    4.             AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
    5. #endif
    6.             return NativeHashMapBase<TKey, TValue>.TryReplaceValue(m_Buffer, key, item, false, m_AllocatorLabel);
    7.         }
    8.  
    9. public static unsafe bool TryReplaceValue(NativeHashMapData* data, TKey key, TValue item, bool isMultiHashMap,
    10.             Allocator allocation)
    11.         {
    12.             TValue tempItem;
    13.             NativeMultiHashMapIterator<TKey> tempIt;
    14.             if (isMultiHashMap)
    15.                 return false;
    16.             return TryReplaceFirstValueAtomic(data, key, item, out tempIt);
    17.         }
    18.        
    19.         public static unsafe bool TryReplaceFirstValueAtomic(NativeHashMapData* data, TKey key, TValue item,
    20.             out NativeMultiHashMapIterator<TKey> it)
    21.         {
    22.             it.key = key;
    23.             if (data->allocatedIndexLength <= 0)
    24.             {
    25.                 it.EntryIndex = it.NextEntryIndex = -1;
    26.                 return false;
    27.             }
    28.             // First find the slot based on the hash
    29.             int* buckets                      = (int*) data->buckets;
    30.             int  bucket                       = key.GetHashCode() & data->bucketCapacityMask;
    31.             it.EntryIndex = it.NextEntryIndex = buckets[bucket];
    32.             return TryReplaceNextValueAtomic(data, item, ref it);
    33.         }
    34.  
    35.         public static unsafe bool TryReplaceNextValueAtomic(NativeHashMapData* data, TValue item,
    36.             ref NativeMultiHashMapIterator<TKey> it)
    37.         {
    38.             int entryIdx = it.NextEntryIndex;
    39.             it.NextEntryIndex = -1;
    40.             it.EntryIndex     = -1;
    41.             if (entryIdx < 0 || entryIdx >= data->keyCapacity)
    42.                 return false;
    43.             int* nextPtrs = (int*) data->next;
    44.             while (!UnsafeUtility.ReadArrayElement<TKey>(data->keys, entryIdx).Equals(it.key))
    45.             {
    46.                 entryIdx = nextPtrs[entryIdx];
    47.                 if (entryIdx < 0 || entryIdx >= data->keyCapacity)
    48.                     return false;
    49.             }
    50.             it.NextEntryIndex = nextPtrs[entryIdx];
    51.             it.EntryIndex     = entryIdx;
    52.  
    53.             // Write the value
    54.             UnsafeUtility.WriteArrayElement(data->keys, entryIdx, it.key);
    55.             UnsafeUtility.WriteArrayElement(data->values, entryIdx, item);
    56.             return true;
    57.         }
    Maded on Collections version 0.0.9-preview.11.
    For using just replace NativeHashMap.cs inside your ProjectFolder\Library\PackageCache\com.unity.collections@0.0.9-preview.11\Unity.Collections location.
     

    Attached Files:

  2. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Thx and bye bye tryget - remove - tryadd ;)
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    This is great but I'm not a fan of replacing the source in packages. Conveniently I already have an imposter for the hashmaps from the work I did iterating them.

    Here is the same code but as an extension method so it doesn't require you to replace anything in the package, without using any reflection

    Code (CSharp):
    1.     /// <summary>
    2.     /// The NativeHashMapExtensions.
    3.     /// </summary>
    4.     public static class NativeHashMapExtensions
    5.     {
    6.         /// <remarks>
    7.         /// Based off work by eizenhorn https://forum.unity.com/threads/nativehashmap-tryreplacevalue.629512/.
    8.         /// </remarks>
    9.         public static unsafe bool TryReplaceValue<TKey, TValue>(this NativeHashMap<TKey, TValue> hashMap, TKey key, TValue item)
    10.             where TKey : struct, IEquatable<TKey>
    11.             where TValue : struct
    12.         {
    13.             var imposter = (NativeHashMapImposter<TKey, TValue>)hashMap;
    14.  
    15. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    16.             AtomicSafetyHandle.CheckWriteAndThrow(imposter.Safety);
    17. #endif
    18.             return NativeHashMapImposter<TKey, TValue>.TryReplaceValue(imposter.Buffer, key, item, false);
    19.         }
    20.     }
    And a few imposters that make it work

    NativeHashMapImposter
    Code (CSharp):
    1. [StructLayout(LayoutKind.Sequential)]
    2.     internal unsafe struct NativeHashMapImposter<TKey, TValue>
    3.         where TKey : struct, IEquatable<TKey>
    4.         where TValue : struct
    5.     {
    6.         [NativeDisableUnsafePtrRestriction]
    7.         public NativeHashMapDataImposter* Buffer;
    8.  
    9. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    10.         public AtomicSafetyHandle Safety;
    11.  
    12.         [NativeSetClassTypeToNullOnSchedule]
    13.         public DisposeSentinel DisposeSentinel;
    14. #endif
    15.  
    16.         public Allocator AllocatorLabel;
    17.  
    18.         public static implicit operator NativeHashMapImposter<TKey, TValue>(NativeHashMap<TKey, TValue> hashMap)
    19.         {
    20.             var ptr = UnsafeUtility.AddressOf(ref hashMap);
    21.             UnsafeUtility.CopyPtrToStructure(ptr, out NativeHashMapImposter<TKey, TValue> imposter);
    22.             return imposter;
    23.         }
    24.  
    25.  
    26.         internal static bool TryReplaceValue(NativeHashMapDataImposter* data, TKey key, TValue item, bool isMultiHashMap)
    27.         {
    28.             if (isMultiHashMap)
    29.             {
    30.                 return false;
    31.             }
    32.  
    33.             return TryReplaceFirstValueAtomic(data, key, item, out _);
    34.         }
    35.  
    36.         private static bool TryReplaceFirstValueAtomic(NativeHashMapDataImposter* data, TKey key,
    37.             TValue item, out NativeMultiHashMapIteratorImposter<TKey> it)
    38.         {
    39.             it.key = key;
    40.             if (data->AllocatedIndexLength <= 0)
    41.             {
    42.                 it.EntryIndex = it.NextEntryIndex = -1;
    43.                 return false;
    44.             }
    45.  
    46.             // First find the slot based on the hash
    47.             int* buckets = (int*)data->Buckets;
    48.             int bucket = key.GetHashCode() & data->BucketCapacityMask;
    49.             it.EntryIndex = it.NextEntryIndex = buckets[bucket];
    50.             return TryReplaceNextValueAtomic(data, item, ref it);
    51.         }
    52.  
    53.         private static bool TryReplaceNextValueAtomic(NativeHashMapDataImposter* data, TValue item, ref NativeMultiHashMapIteratorImposter<TKey> it)
    54.         {
    55.             int entryIdx = it.NextEntryIndex;
    56.             it.NextEntryIndex = -1;
    57.             it.EntryIndex = -1;
    58.             if (entryIdx < 0 || entryIdx >= data->Capacity)
    59.             {
    60.                 return false;
    61.             }
    62.  
    63.             int* nextPtrs = (int*)data->Next;
    64.             while (!UnsafeUtility.ReadArrayElement<TKey>(data->Keys, entryIdx).Equals(it.key))
    65.             {
    66.                 entryIdx = nextPtrs[entryIdx];
    67.                 if (entryIdx < 0 || entryIdx >= data->Capacity)
    68.                 {
    69.                     return false;
    70.                 }
    71.             }
    72.  
    73.             it.NextEntryIndex = nextPtrs[entryIdx];
    74.             it.EntryIndex = entryIdx;
    75.  
    76.             // Write the value
    77.             UnsafeUtility.WriteArrayElement(data->Keys, entryIdx, it.key);
    78.             UnsafeUtility.WriteArrayElement(data->Values, entryIdx, item);
    79.             return true;
    80.         }
    81.     }
    NativeHashMapDataImposter
    Code (CSharp):
    1. [StructLayout(LayoutKind.Sequential)]
    2.     internal unsafe struct NativeHashMapDataImposter
    3.     {
    4.         public byte* Values;
    5.         public byte* Keys;
    6.         public byte* Next;
    7.         public byte* Buckets;
    8.         public int Capacity;
    9.  
    10.         public int BucketCapacityMask; // = bucket capacity - 1
    11.  
    12.         // Add padding between fields to ensure they are on separate cache-lines
    13.         private fixed byte padding1[60];
    14.  
    15.         public fixed int FirstFreeTLS[JobsUtility.MaxJobThreadCount * IntsPerCacheLine];
    16.         public int AllocatedIndexLength;
    17.  
    18.         // 64 is the cache line size on x86, arm usually has 32 - so it is possible to save some memory there
    19.         public const int IntsPerCacheLine = JobsUtility.CacheLineSize / sizeof(int);
    20.  
    21.  
    22.     }
    NativeMultiHashMapIteratorImposter
    Code (CSharp):
    1.     internal struct NativeMultiHashMapIteratorImposter<TKey>
    2.         where TKey : struct
    3.     {
    4.         public TKey key;
    5.         public int NextEntryIndex;
    6.         public int EntryIndex;
    7.  
    8.         public static unsafe implicit operator NativeMultiHashMapIteratorImposter<TKey>(NativeMultiHashMapIterator<TKey> it)
    9.         {
    10.             var ptr = UnsafeUtility.AddressOf(ref it);
    11.             UnsafeUtility.CopyPtrToStructure(ptr, out NativeMultiHashMapIteratorImposter<TKey> imposter);
    12.             return imposter;
    13.         }
    14.     }
     
    Last edited: Feb 14, 2019
    dCalle and sngdan like this.
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Yep, cool too :) also we have third solution - just put NHM into project with in our namespace and it’s not require any wrappers or source replacement :D
     
  5. dCalle

    dCalle

    Joined:
    Dec 16, 2013
    Posts:
    55
    wow, what I actually need. Does this still work?
     
  6. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    You don't need it now, it's already implemented in Collections months ago.
     
    dCalle likes this.