Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Efficient random values in job

Discussion in 'Entity Component System' started by rkas1222, Oct 6, 2018.

  1. rkas1222

    rkas1222

    Joined:
    Aug 9, 2017
    Posts:
    6
    Best approach for efficiently generating random values in a parallel job?

    I've used ThreadLocal System.Random objects in jobs, without ECS, and that seems to work. But my understanding is that doing anything with managed memory inside of a job is potentially very slow -- presumably from under-the-covers locking.

    Alternatively, if there are guarantees on allocations within System.Random (i.e. there aren't many/any), perhaps that's good enough, given that they are thread local. But if there's a better or more idiomatic way to do this with jobs, I'd like to know.
     
    tonytopper likes this.
  2. dartriminis

    dartriminis

    Joined:
    Feb 3, 2017
    Posts:
    157
    There is a Unity.Mathematics.Random struct available for use. There's also Unity.Mathematics.noise if that floats your boat. ;)
     
  3. rkas1222

    rkas1222

    Joined:
    Aug 9, 2017
    Posts:
    6
    Looks perfect. Thanks!
     
  4. Ryetoast

    Ryetoast

    Joined:
    Mar 8, 2015
    Posts:
    48
    Are there any good examples of using Unity.Mathematics.Random out there? I'm unclear on what good practices are for seeding, or dealing with random values inside of a parallel job, like IJobProcessComponentData.

    From testing, similar seeds produce similar results, which means;
    • Using something like the index as a seed offset isn't good enough
    • Using tick count would be bad if you are creating these structs on the fly
    The solutions I can think of off the top of my head are:
    • Trying to use a single Unity.Mathematics.Random instance for most things that just lives for the duration of the program, which doesn't really work with parallel jobs since getting a random is a modifying action.
    • Having an instance of Unity.Mathematics.Random on each entity that is going to be doing random things. Unfortunately this still has the issue that I basically need to manually randomize the seed in order to not have entities have very similar starting patterns.
    Is there some simple solution I'm missing?
     
    tonytopper, DragonCoder and Mikael-H like this.
  5. Ryetoast

    Ryetoast

    Joined:
    Mar 8, 2015
    Posts:
    48
    After spending some time messing around with it, the best I could come up with is:
    1. Use an [Inject] and IJobParallelFor instead of IJobProcessComponentData so I can know how many seeds I need
    2. Generating a NativeArray<uint> of seeds using (uint)UnityEngine.Random.Range(int.MinValue, int.MaxValue)
    3. Make new Unity.Mathematics.Random(Seed[index]) inside the job
    I couldn't just directly make a NativeArray<Unity.Mathematics.Random> and then call InitState because the constructor errors on state not being initialized yet... (checks the state member variable isn't 0). Still doesn't seem ideal to me even if I could do that, but I guess it works.
     
    Mikael-H likes this.
  6. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    EDIT: I've found that the method below does not produce entirely random numbers for some reason. If anyone can spot the error I'd be very grateful. So for now either produce random numbers beforehand or set an array of random generators with their seeds set.


    Thanks for the inpyt @Ryetoast! I ended up setting just a base seed for the job and then adding the index value to this seed, that way I don't need to assign an extra native array.

    Code (CSharp):
    1. private struct BulletHitHandleJob : IJobForEachWithEntity<BulletMoveData>
    2.         {
    3.             [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<RaycastHit> RaycastHits;
    4.             [ReadOnly] public EntityCommandBuffer.Concurrent CommandBuffer;
    5.             [ReadOnly] public uint BaseSeed;
    6.  
    7.             public void Execute(Entity bulletEntity, int index, ref BulletMoveData bulletSpeed)
    8.             {
    9.                 var seed = (uint)(BaseSeed + index);
    10.                 var rnd = new Unity.Mathematics.Random(seed);
    11.  
    12.                 if (RaycastHits[index].distance > 0)
    13.                 {
    14.                     var bulletHitData = new BulletHitData
    15.                     {
    16.                         Hit = RaycastHits[index],
    17.                         Damage = rnd.NextInt(1,50),
    18.                         Attacker = bulletSpeed.Shooter,
    19.                         Impact = -bulletSpeed.Direction * bulletSpeed.MetersPerSecond,
    20.                 };
    21.                  
    22.                     CommandBuffer.AddComponent(index, bulletEntity, bulletHitData);
    23.                     CommandBuffer.RemoveComponent<BulletMoveData>(index, bulletEntity);
    24.                 }
    25.             }
    26.         }
     
    Last edited: Aug 8, 2019
    pakfront likes this.
  7. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    Ahh I should have read your previous post more carefully! :) Seems very strange to me that similar seeds produce similar values, but my observations are the same as yours!
     
  8. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    610
    EDIT:
    You are correct, and I have updated my example!



    The way I do it:

    Code (CSharp):
    1. private struct AntMoveJob : IJobEntityBatch
    2. {
    3.     public uint seed;
    4.  
    5.     [BurstCompile]
    6.     public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
    7.     {
    8.         if (uint.MaxValue - (uint)batchIndex + 1 == seed) // Make sure our resulting seed isn't 0
    9.         {
    10.             seed++;
    11.         }
    12.         Random random = new Random(seed + (uint)batchIndex);
    13.     }
    14. }
    15.  
    16. protected override void OnUpdate()
    17. {
    18.     var antMoveJob = new AntMoveJob
    19.     {
    20.         seed = (uint)(UnityEngine.Random.value * uint.MaxValue); // Get a random seed between 0 and uint.MaxValue inclusive
    21.     };
    22. }
    This gives me a random seed every time since UnityEngine.Random "is seeded exactly once on process start, and after that is left entirely under script control." Learn More

    Still though... this is pretty cumbersome. I hope one day Unity / @Joachim_Ante will give us an official solution to this problem but for now the above should be a catch-all.
     
    Last edited: Oct 12, 2021
    tonytopper likes this.
  9. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    862
    What is wrong with Unity.Mathematics.Random I have been using that.
     
  10. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    math.Random is a value type. Each job get's a copy having the same initial seed.
     
  11. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Generally, I make math.Random per entity. I can use the formula: "seed + count_of_get_random_values * index" as that is the correct usage of seed in the parallel cases as far as I know.
     
  12. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Shouldn't one have a large arrays of random values to pass to jobs like one would do in a shader?
     
  13. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    277
    Probably depends on what randomness quality you require. For my purposes Random.CreateFromIndex is enough - you can pass in the array index, the
    entityInQueryIndex
    or the
    [NativeSetThreadIndex]
    and you'll be good most of the time. If you need quality randomness, then yeah, you gotta smuggle numbers around.
     
    CaseyHofland and thelebaron like this.
  14. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,223
    Random number generators are extremely difficult to get right in DOTS because what you really want is a new random number per call per entity per job per frame. With Unity's
    Random
    , you typically have to sacrifice multi-threading, determinism, or bandwidth to pull this off. However, there was a GDC presentation that provided a solution for this kind of problem, and I have adapted that solution for DOTS: https://github.com/Dreaming381/Lati...4.2/Documentation~/Core/Rng and RngToolkit.md

    If you don't want to bring in the whole framework, you just have to copy Rng.cs and RngToolkit.cs from here: https://github.com/Dreaming381/Latios-Framework/tree/v0.4.2/Core/Core/Math
     
  15. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    969
    We use a variant of something like this.
     
    CaseyHofland, xVergilx and apkdev like this.
  16. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    There's even a simpler trick (if you're using Entities), although it uses some extra memory.
    You can always keep a copy of local Mathematics.Random inside manipulated data / attached to the entity.

    Initialize initial value for the Random / component data during authoring (e.g. by providing initial seed from UnityEngine.Random.Range).

    Voila, you can now use it inside jobs and its 100% burst compatible and multi-thread safe. Don't forget to update Random data after you're done generating next values. Otherwise it would produce same values again.

    Bonus of this approach is that each entity has pre-determined random, so its quite easy to serialize / deserialize exact state by saving whole component (including Random).

    And if I'm not mistaken Mathematics.Random is pretty much one uint, so IMO its not that big of a deal.
     
    Last edited: Oct 14, 2021
    Neiist, Nith666, SolidAlloy and 3 others like this.
  17. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    610
    Didn't I do that in my example here? As you can see, there's still 3 lines of code at the top of the Execute method to increment that base seed. If this is not there, all your jobs will be working from the same base seed. The result of this is that your ants have random movement, but some ants will have the same "random" movement.

    It's also cumbersome.
     
  18. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    No, not quite.

    You're using one random per job. Whereas its possible to use Random per Entity.
    (Without if conditions and figuring out whether seed is correct)
     
    CaseyHofland likes this.
  19. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    610
    I see what you mean now, yes that works too!

    I was also trying to see if you could create an ISharedComponentData out of it but I'm having trouble working with SharedComponentTypeHandle inside of IJobEntityBatch. *Siiiiight... DOTS... I love you but I hate you but I love you.
     
    Nith666 likes this.
  20. Armegalo

    Armegalo

    Joined:
    May 14, 2018
    Posts:
    30
    Been using this inside jobs - not too bad

    Code (CSharp):
    1. Unity.Mathematics.Random randomGen = new Unity.Mathematics.Random((uint)((i + 1) * seed) + 1);
    2. float r=randomGen.NextFloat(x,y);
    3.