Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Update values of NativeMultiHashMap?

Discussion in 'Entity Component System' started by nyanpath, Dec 16, 2020.

  1. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
  2. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    NativeMultiHashMap<int, NativeArray<T>>
    That's a nested native container, it should not me possible. :confused:
     
    Last edited: Dec 16, 2020
  3. jasonboukheir

    jasonboukheir

    Joined:
    May 3, 2017
    Posts:
    80
    In the link you sent, there's a method,
    SetValue(TValue, NativeMultiHashMapIterator<TKey>)


    Try that.
     
    nyanpath likes this.
  4. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    It is possible, I just tried it.

    This seems that I would have to make a copy of the array, iterate over it to update the values, and then put it back into the NativeMultiHasjMap?
     
  5. jasonboukheir

    jasonboukheir

    Joined:
    May 3, 2017
    Posts:
    80
    Nested native containers should give you an error when they're burst compiled. In general, you can't do things like
    NativeArray<NativeArray<T>>
    .

    Also it seems like you have a misunderstanding of what the
    NativeMultiHashMap
    is.

    NativeMultiHashMap<int, NativeArray<T>>
    is a map from a single
    int
    key to multiple
    NativeArray<T>
    values.

    I think you want
    NativeMultiHashMap<int, T>
    , which is a map from a single
    int
    key to multiple
    T
    values.
     
    nyanpath likes this.
  6. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    I hope you are getting paid by Unity for this. There should be a tip jar where whenever someone does a better job at explaining than Unity's documentation, Unity pays them for their time.
     
  7. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    195
    Anyone have an example for SetValue(TValue, NativeMultiHashMapIterator<TKey>) ? Can't seem to figure this out.
     
    alleballe90 and nyanpath like this.
  8. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    The documentation for NativeMultiHasjMap is barer than the bone it is attached to.
     
  9. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    195
    would be nice to have a AddForKey(TKey, TValue), RemoveForKey(TKey, TValue), InsertAtIndexForKey(TKey, int, TValue) etc.

    Basically all the normal List<> functions without having to use an iterator. Will probably immediatley make extensions for this once I can get an example here to figure this out.
     
    nyanpath likes this.
  10. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    Every public method is pretty well documented...

    upload_2020-12-18_11-37-13.png
     
  11. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    297
    I swear there was once some very detailed information and documentation on NativeMultiHashMaps (NMHMs) several months, if not a year ago. Wonder where they went.

    Anyways, I might be able to provide some idea on how to use this.

    Lets go back to the usage of NMHMs in general.

    (I'm away from my main coding computer so I'm writing this in the forum editor, please excuse me is I mistype a method or property from memory.)

    Designate the NMHM. NativeMultiHashMaps, NativeHashMaps, and NativeHashSets are like NativeLists in that they allocate more space if the current size is reached IN THE MAIN THREAD. You'll have a lot of out of bounds errors if you try adding more than the listed capacity in a scheduled job (IJob, IJobParallels, or EntitiesForEach, anything that runs off the main thread).

    Code (CSharp):
    1. var nativeMulti = new NativeMultiHashMap<int, float>(1, Allocator.TempJob);
    We then add stuff to this. You can do so in the Main Thread using .Add(int, float) or inside a parallel job using the .ToConcurrent() and then .Add() to it as well [I swear they renamed the ToConcurrent(), dont remember to what].

    Note, the order in which values exist and are accessed in a NMHM is not guarenteed. Especially if done in parallel.

    Code (CSharp):
    1. nativeMulti.Add(1, 0.1f);
    2. // Next addition resizes the NMHM. I believe it's by power of two.
    3. // Dont know where I got that info from.
    4. nativeMulti.Add(2, 0.2f);
    5. nativeMulti.Add(1, 0.5f);
    6. nativeMulti.Add(1, 0.2f);
    To access a NMHM is fairly boilerplate. First is to check if the key value exists.

    Code (CSharp):
    1. if (!nativeMulti.TryGetFirstValue(key, out var value, out var iterator))
    2.     // TryGetFirstValue returned false.
    3.     // The key value does not exist in the NMHM.
    4.     // Skip the rest of the access code.
    5.     return;
    A
    key
    value of 3 will return false. Lets say the key is instead 1. It'll return 0.1, 0.5, or 0.2. If this is the entirety of the code altering nativeMulti, it's probably 0.1 but that's not guarenteed.

    Now, let say we want to swap the value of 0.5 under key = 1 with 1.2. We'll need a do - while loop. Like a while loop, it sets the condition check to the end of the loop code run instead of before.

    This is important because we already checked if the NMHM contained the key and recieved the first value. We cant use a normal While loop using .TryGetFirstValue() because it'll just keep getting that first value and returning true infinitely.

    Code (CSharp):
    1. do {
    2.     // Here we check if the value is the one we want to swap out.
    3.     if (value == 0.5f) {
    4.         // This is where we use .SetValue() using the iterator
    5.         //    that already has the key and current value
    6.         // stored inside to replace it, 0.5, with the target value, 1.2.
    7.         nativeMulti.SetValue(1.2f, iterator);
    8.         break;
    9.     }
    10. } while (nativeMulti.TryGetNextValue(out value, ref iterator));
    Done, that's all you need to replace a single specific value in a NMHM. An if condition and a do-while loop.

    Iterator, I think it's of type NativeMultiHashMapIterator, is a managed type. No need to worry about disposing or doing anything to it after you've done running through a NMHM with it. C# will deal with it.

    You can also iterate through a NMHM in a IJobParallel as a [ReadOnly] using this If -> Do - While loop. You just wont be able to set/swap any values unless you use a two NMHM buffer system, one read one write.
     
    Last edited: Dec 18, 2020
  12. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    195
    @Kmsxkuse Thanks for the explanation.

    They use .AsParallelWriter() now.

    From the documentation:
    So if you did:

    Code (CSharp):
    1. nativeMulti.Add(0, 1);
    2. nativeMulti.Add(0, 2);
    Would this Add 2 values at Key 0, or would this throw an error?

    If it just adds another value to the Key, does it do this in a reliable order from the time it was added? Or is there a chance these values could be out of order?

    Thanks,
     
    RendergonPolygons likes this.
  13. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    297
    It will add two values. This is the entire reason why NativeMultiHashMap exists. I dont know where you got that documentation from but if it was true, a NativeMultiHashMap would be no different from a NativeHashMap.
     
  14. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    This is what I wrote as a test:
    Code (CSharp):
    1.     [BurstCompile]
    2.     public struct CreateMultiHashMapJob : IJobFor
    3.     {
    4.         [WriteOnly]
    5.         [NativeDisableParallelForRestriction]
    6.         public NativeMultiHashMap<int, float6> OutData;
    7.  
    8.         [ReadOnly]
    9.         public NativeArray<int> Amounts;
    10.  
    11.         [ReadOnly]
    12.         public float Multiplier;
    13.  
    14.         public void Execute(int index)
    15.         {
    16.             int amount = Amounts[index];
    17.  
    18.             for(int c = 0; c < amount; ++c)
    19.             {
    20.                 OutData.Add(index, new float6(new float3(c * Multiplier, (c + 1) * Multiplier, (c - 1) * Multiplier), new float2(Multiplier - (c * Multiplier), Multiplier + (c * Multiplier)), c));
    21.             }
    22.         }
    23.     }
    Which is used like so:
    Code (CSharp):
    1.     private void CreateJob()
    2.     {
    3.         _hashJob = new CreateMultiHashMapJob
    4.         {
    5.             Multiplier = UnityEngine.Random.value,
    6.             OutData = _testMultiData,
    7.             Amounts = _amounts
    8.         };
    9.  
    10.         _dataHandle = _hashJob.ScheduleParallel<CreateMultiHashMapJob>(_amounts.Length, 32, default);
    11.         JobHandle.ScheduleBatchedJobs();
    12.         _jobScheduled = true;
    13.  
    14.         Debug.Log("Jobbe creat'd.");
    15.     }
    float6 is a sick and evil joke struct I created for my own cruel and twisted testing:
    Code (CSharp):
    1.     public struct float6
    2.     {
    3.         public float3 Vertex;
    4.         public float2 UV;
    5.  
    6.         public int Index;
    7.  
    8.         public float6(float3 vertex, float2 uv, int index)
    9.         {
    10.             Vertex = vertex;
    11.             UV = uv;
    12.             Index = index;
    13.         }
    14.     }
    And this is how it reads the data on the thread of main:
    Code (CSharp):
    1. for (int i = 0; i < _amounts.Length; ++i)
    2.             {
    3.                 bool found = _hashJob.OutData.TryGetFirstValue(i, out float6 item, out NativeMultiHashMapIterator<int> it);
    4.              
    5.                 if(found)
    6.                 {
    7.                     Debug.LogWarning(i + " | " + it.GetHashCode() + "\n" + item.Vertex + " | " + item.UV + " | " + item.Index);
    8.                 }
    9.              
    10.                 while (found)
    11.                 {
    12.                     found = _hashJob.OutData.TryGetNextValue(out item, ref it);
    13.  
    14.                     if(found)
    15.                     {
    16.                         Debug.Log(i + " | " + count + "\n" + item.Vertex + " | " + item.UV + " | " + item.Index);
    17.                     }
    18.                 }
    19.             }
     
    Last edited: Dec 18, 2020
    SundownStudio likes this.
  15. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    195
    It's in the offical Unity documentation for NativeMultiHashMap lol they probably just copy pasted it from the NativeHashMap doc.... I guess I should have just tested it but this part of the documentation screwed me up.

    Anyways thanks for the info. I can now continue :)
     
    keypax, tonytopper and mannyhams like this.
  16. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    297
    Oh no, dont use that tag for NativeMultiHashMaps. Only use it for NativeArrays where you're using it along single index read write values.

    Use OutData.AsParallelWriter() in the struct assigning the variable to NativeMultiHashMap.ParallelWriter OutData in the main thread.

    You can keep the [WriteOnly] tag.

    Also please use the If -> Do - While structure I laid out.

    Also, dont use a IJobParallel stuct as data storage. Inputs into a IJobParallel struct are passed by reference and you should just use the input OutData original NativeMultiHashMap after you call .Complete() on the JobHandle.

    Welcome aboard Unity's DOTS experience. Where documentation either doesn't exist, does exist but is outdated by years, or, worse, straight up wrong.

    I learned all of this from painful trial and error experimentation. Glad to be of help.
     
    nyanpath likes this.
  17. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    Jobs are structs. Inputs to all jobs passed by value, not by reference. The reason you getting results back by native containers is only because underhood they just store pointers to data in allocated for container memory, which is just number points to a memory address, but the container itself passed by value.
     
    toomasio, nyanpath and Kmsxkuse like this.
  18. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    297
    Oh yea. That makes sense. See, I'm learning something new every day with DOTS. Luckily it doesnt require any changes in my code.
     
  19. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    Thank you all for your help in here, however I went for a plain NativeList<T>-solution that solved all the issues I worried about.

    Here is some blazing fast multiple procedural mesh creation using IJobFor, the new mesh-API and some extremely uncouth usage of NativeLists:
    citening4.gif
    Compared to my previous singlethreaded solution with a coroutine this really feels like I am cheating. I had no idea how much power resides in the Job System and how well it suits my intents and purposes.
     
  20. SuperFranTV

    SuperFranTV

    Joined:
    Oct 18, 2015
    Posts:
    140
    how do you do Parallel writing to NativeList?
     
  21. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,574
    You should really create separate thread on that topic, rather necroing and hijacking existing one.
     
  22. inSight01

    inSight01

    Joined:
    Apr 18, 2017
    Posts:
    86
    I'm curious how one might be able to alter the value of a particular element in the array if the index is known. For example.

    Code (CSharp):
    1. public Dictionary<int, int[]> dictionaryOfArrays= new Dictionary<int, int[]>();
    If I know the index of the element in the array I want to change then I can do something like this.

    Code (CSharp):
    1. dictionaryOfArrays[key][index] = 10;
    The array could have multiple elements with the same value but I may only want to change one specific one.

    Let's say I have multiple grids. Each grid is assigned a TKey. Each cell of the grid is TValue. What if I want to change a value based on X and Y coordinates?

    In a NativeArray for a single grid I can do something like.

    Code (CSharp):
    1. var index = x + gridwidth * y;
    2. nativeArray[index] = value
    The grid could have many values that are the same. So doing a loop over the entire array and stopping at the index of the first value that matches may not give me the index I want. Also, looping over the entire array seems suboptimal especially if the index of the value you want to change is already known.

    Is there a better way of doing this or is this a limitation of NPMHMs and I should just stick to using a Dictionary?

    EDIT: I suppose one way I could do this is to use TKey as the grid index and TValue can hold the value of that index with each element representing the cell value of different grids. This would make each TValue unique so looping through the TValue's should be easy enough and fast.

    But then, what if I need to loop through every single TKey and modify every single TValue. I guess one way to do that would be to loop through every TValue of every TKey and add it to a Native List. Then alter the values in a job and then clear and re-enter the values into the NPMHM. Seems like a lot of effort for something that seems so simple.
     
    Last edited: Jan 8, 2023
  23. fas3r_

    fas3r_

    Joined:
    May 6, 2021
    Posts:
    11
    Hello @inSight01 ,

    I'm looking at making something similar, did you make any finding :) ?
     
  24. SundownStudio

    SundownStudio

    Joined:
    Apr 19, 2017
    Posts:
    11
    I'm dealing with this right now too..
    EDIT: so I asked ChatGPT and found a better solution than what I'd originally posted-

    Code (CSharp):
    1.  
    2. int keyToRemoveFrom = 1;
    3. int valueToRemove = 20;
    4.  
    5. // Iterate over key-value pairs with the given key
    6. if (multiHashMap.TryGetFirstValue(keyToRemoveFrom, out int currentValue, out NativeMultiHashMapIterator<int> iterator)) {
    7.     do {
    8.         // If the current value is the one to remove, remove it
    9.         if (currentValue == valueToRemove) {
    10.             multiHashMap.Remove(iterator);
    11.             break;
    12.         }
    13.     } while (multiHashMap.TryGetNextValue(out currentValue, ref iterator));
    14. }
     
    Last edited: May 2, 2023
  25. inSight01

    inSight01

    Joined:
    Apr 18, 2017
    Posts:
    86
    The above method has issues that didn't work for me. Firstly, it's slow. You have to iterate over the array until you find a value that matches what you are looking for. It's akin to using something like List<T>.Remove(T). From my own testing it proved slower than using a Dictionary even when using the multi hashmap inside a burst compiled job when the array was particularly large. Secondly, it only removes the first instance of the value being checked. If you have multiple indices with the same value but you only wish to remove one particular one than this method does not work.