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

Question Passing a NativeHashMap from one Job to the next seems to require blocking main thread?

Discussion in 'Entity Component System' started by Zergling103, Jan 11, 2022.

  1. Zergling103

    Zergling103

    Joined:
    Aug 16, 2011
    Posts:
    392
    I have two jobs.
    1. Job A populates a NativeHashMap with values.
    2. Job B enumerates through the NativeHashMap.
    Job B is scheduled with a dependency on Job A. (This means, in the Job System, when Job A completes it'll automatically launch Job B without action from the main thread. It will prevent Job A and Job B from running at the same time.)

    The intention is to schedule both Job A and Job B without needing Job A to complete before scheduling Job B.

    However, I've come to an impasse where it is not certain on how to do this, and I don't want to waste time exhausting other ideas that might lead to more dead ends.

    Here are the things I've tried:

    Approach: Getting the Enumerator of the NativeHashMap inside Job B.
    Result: Error, cannot get the Enumerator in a Job thread.

    Code (CSharp):
    1. struct JobB : IJob
    2. {
    3.     [ReadOnly]
    4.     NativeHashMap<T1, T2> HashMap;
    5.  
    6.     public void Execute()
    7.     {
    8.         // ERROR: Can't access GetEnumerator in a Job thread.
    9.         // Note, this returns an instance of struct NativeHashMap<T1,T2>.Enumerator
    10.         // and not System.Collections.IEnumerator.
    11.         var enumerator = HashMap.GetEnumerator();
    12.  
    13.         while(enumerator.MoveNext())
    14.         {
    15.             var current = enumerator.Current;
    16.  
    17.             { ... }
    18.         }
    19.     }
    20. }
    Approach: Get the Enumerator on the Main Thread, pass it to Job B.
    Result: Error, cannot access the Enumerator until Job A is finished.

    Code (CSharp):
    1. JobA.OutputHashMap = HashMap;
    2. var jobAHandle = JobA.Schedule();
    3.  
    4. // Uncomment the following line to prevent the error
    5. // and to also completely miss the point of using threads:
    6. // jobAHandle.Complete();
    7.  
    8. // ERROR: Must wait until JobA is done before using HashMap.GetEnumerator().
    9. JobB.InputHashMapEnumerator = HashMap.GetEnumerator();
    10. JobB.Schedule(jobAHandle);
    Here are things I'm considering:

    Approach: Get the Enumerator before dispatching Job A.
    Doubts: Will the Enumerator even be valid here? It'll probably complain about the collection being modified by Job A when used by Job B.

    Approach: Using GetKeyArray() and GetValueArray(), pass those arrays to Job B.
    Doubts: Can you even use these methods in a Job thread? Getting the arrays in the Main Thread also would make the threading largely pointless as it'd require Job A to complete.

    Approach: Store the HashMap's values in a separate list, let the HashMap values be indices in that list.
    Doubts: C'mon, seriously? There's gotta be a way that is less of a pain in the ass.

    So yeah, I'm kind of stuck. I'm afriad to touch this stuff in fear of spending more time just to face the motivation-sucking sting of disappointment yet again. ;;D

    Ideally I'd want to make the work of Job B parallelized across every element of the collection. I'm guessing there's no reasonable way to do that over a NativeHashMap, so copying to arrays might be inevitable.

    If anyone has had luck doing this sort of thing, I'd love to hear from you about it!

    Thanks!
     
    Last edited: Jan 14, 2022
  2. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    943
    If jobs A and B runs at the same time, it will lead to bugs as A is writing to the hashmap while B is also reading. Job B might not encounter the items that were just added. I really don't recommend this. What's wrong with chaining job A then B? Try it first and maybe the running time is already acceptable to you especially if both jobs would be Burst compiled and multithreaded.
     
  3. Zergling103

    Zergling103

    Joined:
    Aug 16, 2011
    Posts:
    392
    No, Job B has a dependency on Job A. It'll run when Job A finishes automatically by the job system.

    The problem is I can't schedule Job A and Job B using the **dependency** chain, instead of waiting for Job A to finish.

    I've edited the post to be more clear.
     
    Last edited: Jan 12, 2022
  4. Baggers_

    Baggers_

    Joined:
    Sep 10, 2017
    Posts:
    97
    [EDIT] yup, my answer was out of date, see s_schoener's answer below

    @Zergling103 as you have well spotted, NativeHashMap.GetEnumerator() is the issue. The reason is that GetEnumerator involves accessing the hash-map (more specifically SetBumpSecondaryVersionOnScheduleWrite).

    Now my knowledge on this is probably out of date, but it looks to me like you are doing the right thing and are just being bitten by a ugly part of the current API around enumerators.

    Instead you'd probably need to use GetUniqueKeyArray inside the job with a Temp or Persistent allocator. On recent version of Burst this may work thanks to improvements handling tuples. However if it is still an issue you can use this extension method instead (it should be the same except for the signature)

    Code (CSharp):
    1. [BurstCompile]
    2. public static NativeArray<TKey> GetUniqueKeys<TKey, TValue>(
    3.     this NativeHashMap<TKey, TValue> hashMap,
    4.     Allocator allocator,
    5.     out int keyCount)
    6.     where TKey : struct, IEquatable<TKey>, IComparable<TKey>
    7.     where TValue : struct
    8. {
    9.     var withDuplicates = hashMap.GetKeyArray(allocator);
    10.     withDuplicates.Sort();
    11.     var uniques = withDuplicates.Unique();
    12.     keyCount = uniques;
    13.     return withDuplicates;
    14. }
    and for fun here is the same for NativeMultiHashMap

    Code (CSharp):
    1. [BurstCompile]
    2. public static NativeArray<TKey> GetUniqueKeys<TKey, TValue>(
    3.     this NativeMultiHashMap<TKey, TValue> hashMap,
    4.     Allocator allocator,
    5.     out int keyCount)
    6.     where TKey : struct, IEquatable<TKey>, IComparable<TKey>
    7.     where TValue : struct
    8. {
    9.     var withDuplicates = hashMap.GetKeyArray(allocator);
    10.     withDuplicates.Sort();
    11.     var uniques = withDuplicates.Unique();
    12.     keyCount = uniques;
    13.     return withDuplicates;
    14. }
    Hopefully though I'm very out of date and everything has been improved. If so I'm looking forward to the answer too!
     
    Last edited: Jan 13, 2022
  5. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    943
    I see. In that case, you should set the NativeHashMap with [ReadOnly] attribute in job B.
    Code (CSharp):
    1. struct JobB : IJob
    2. {
    3.     [ReadOnly]
    4.     NativeHashMap<T1, T2> HashMap;
    5.  
    6.     ...
    7. }
     
  6. s_schoener

    s_schoener

    Unity Technologies

    Joined:
    Nov 4, 2019
    Posts:
    81
    This is the right approach. We do have tests that validate that you can use these enumerators from within a job. An earlier version of collections erroneously had a call to
    Code (CSharp):
    1. AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(ash, true);
    in the GetEnumerator function. What version of collections does this happen with? The latest release should have the fix, see the changelog here: https://docs.unity3d.com/Packages/c...1.1/changelog/CHANGELOG.html#110---2021-10-27.
     
    Baggers_ likes this.
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    Well if he not using Entities then it can work. But if he uses the Entities package - wouldn't. The latest collections package which is available for 2020.3 (as Entities frozen to this version) is 1.0.0-pre.6. BUT collections above 0.15 are not compatible with Entities 0.17 at all. If you upgrade to Collections 0.17.0 entities will complain about
    Words
    , if you upgrade to Collections 1.0.0-pre.3 it will break Jobs 0.8.0, and will force you to upgrade Jobs to 0.11.0, which is also not compatible with Entities because of broken generic restriction for NativeList<T> (
    unmanaged
    in collections and
    struct
    in usage in entities BinarySerialization). Of course, we can make the package local and fix that, but not all ready for that :D
     
    Occuros, Krajca and Enzi like this.
  8. s_schoener

    s_schoener

    Unity Technologies

    Joined:
    Nov 4, 2019
    Posts:
    81
    Ah, that sucks. In that case waiting for 0.50 might be a good option, and in the meantime delete the offending line I called out in the previous post from the method in collections.
     
  9. Baggers_

    Baggers_

    Joined:
    Sep 10, 2017
    Posts:
    97
    When I modify code in a package, the package-manager repairs it on the next load (IIRC). I had assumed that you needed to fork the package to be able to do this. Is that correct?
     
  10. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    478
    You can copy the package from Library/PackageCache into your packagers folder (where the manifest file is) to create a local modifiable version.
     
    Baggers_ likes this.
  11. Baggers_

    Baggers_

    Joined:
    Sep 10, 2017
    Posts:
    97
    Nice, yeah that's what I expected. Cheers
     
  12. Zergling103

    Zergling103

    Joined:
    Aug 16, 2011
    Posts:
    392
    I think it was with Burst 1.4.7 and Jobs Version 0.8.0-preview.23. There is no listing for the Collections package in the package manager and appears to have been included with one of those two packages.

    When I look at the collections package in Project View > Packages > Collections, the version is listed as 0.15.0-preview.21. When I select "View in Package Manager" the package manager appears but does not show anything relevant to the Collections package (it remains unlisted).

    I've recently ran into another annoying exception where I cannot queue up a Sort() job to happen AFTER other jobs finish first (defeating the purpose of supplying a job dependency handle).

    How do I ensure I am using the latest versions of these packages that are compatible with my Unity version? I am using 2020.2.0f1, am I just hosed unless I upgrade Unity? The package manager seems broken as it doesn't list Collections anywhere.
     
  13. DrBoum

    DrBoum

    Joined:
    Apr 19, 2020
    Posts:
    26
    any news you could share about this 0.5 entity release ?
     
  14. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    0.50 release
     
  15. DrBoum

    DrBoum

    Joined:
    Apr 19, 2020
    Posts:
    26
    0.5 and 0.50 are the same i didn't say 0.05
     
  16. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    0.5 isn't the same as 0.50. One has 5 after the dot and the other has a 50. Two different versions.

    Sorry for seemingly nitpicking. I'm not doing it to be mean but it will confuse a lot of people later as you clearly are. The current version is 0.17 and there was a 0.5 in the past:
    https://docs.unity3d.com/Packages/com.unity.entities@0.5/manual/index.html
    so the next version will be 0.50 as it was said here:
    https://forum.unity.com/threads/dots-development-status-and-next-milestones-december-2021.1209727/
     
  17. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    297
    I see you say this everywhere and, while it's a good fight, it's a losing one. I personally just let whoever call the next DOTS version as they wish to. If they say 0.50, you know they've been on the DOTS train since at least U-ECS and the [Inject] era. If they say 0.5, then they're fairly new or not that active.