[SOLVED] Gen random number in Job Execute causes hang

Solved: I did not know that the mathematics library had a random function, and in learning that I also learned that I need to be VERY careful on which functions/libraries I use with DOTS.

This works:
Unity.Mathematics.Random mathRandom = new Unity.Mathematics.Random(88);
int randomValue = mathRandom.NextInt(0, 10);

Also, while I haven’t done it yet, what EizenHorn states is worth looking into.

Hello gang,

I’m having an odd situation where I am creating a NativeList in a Job’s Execute and when I generate a random number number Unity hangs. Task Manager shows 0% CPU and the memory usage does not change. I left it running 10 minutes to see if there is any change to these values, nope. Had to End Task on Unity

int randomValue = UnityEngine.Random.Range(0, 10); //This line uncommented causes Unity to hang

public void Execute(
            DynamicBuffer<AutoLanePointsComponent> autoLanePoints,
            [ReadOnly] ref AutoDetailsComponent autoDetails,
            ref AutoPositionComponent autoPosition,
            ref Translation autoTranslation,
            ref Rotation autoRotation)
        {
            var distance = math.distance(autoPosition.destination, autoTranslation.Value);

            if (distance < reachedPositionDistance)
            {
                autoPosition.currentIndex += 1;

                //If the current position is at the end of the list of points,
                //if so, get the next list from the road/connection
                if (autoPosition.currentIndex >= autoLanePoints.Length)
                {
                    NativeList<int> roadIdents = new NativeList<int>(Allocator.Temp);

                    //autoLanePoints.Clear();  //Before we add new values...

                    //Get the next connection and it's points
                    if (autoPosition.isOnRoad)
                    {
                        for (int i = 0; i <connectionEntities.Length; i++)
                        {
                            var connectionDetails = ConnectionDetailsFromEntity[connectionEntities[i]];

                            if ((connectionDetails.LaneIdentity == autoPosition.laneIdentity) &&
                                (connectionDetails.RoadEntryIdentity == autoPosition.roadIdentity))
                            {
            
                                roadIdents.Add(connectionDetails.RoadExitIdentity);
                            }
                        }

                        if (roadIdents.Length > 0)
                        {
                            //int randomValue = UnityEngine.Random.Range(0, roadIdents.Length - 1);
                            int randomValue = UnityEngine.Random.Range(0, 10);  //LOCK UP HERE

                            //autoPosition.roadIdentity = roadIdents[randomValue];
                        }
                    }

                    autoPosition.isOnRoad = !autoPosition.isOnRoad;
                    autoPosition.currentIndex = 0;

                    roadIdents.Dispose();
                }

                autoPosition.destination = autoLanePoints[autoPosition.currentIndex].value;
                autoPosition.destination.y += autoDetails.yOffset;
            }

            float3 lookVector = autoPosition.destination - autoTranslation.Value;

            if (math.all(lookVector))
            {
                Quaternion rotationLookAt = Quaternion.LookRotation(lookVector);
                autoRotation.Value = rotationLookAt;
            }
          
            float3 smoothedPosition = math.lerp(autoTranslation.Value, autoPosition.destination, autoDetails.speed * deltaTime);
            autoTranslation.Value = smoothedPosition;
}

Unsure off the top of my head why hanging would occur. AN error might happen sure, hanging is odd.

My recommendation: Look into the new math library’s random. It is a lot more performant and deterministic

1 Like

Use Unity.Mathematics.Random for random in bursted job, but don’t forget that this is struct and copied by value. You should prepare some NativeArray of random’s with count equal worker thread count and pass this array to your job and after changing - update this array.
(based on something like this Mathematics.Random with in IJobProcessComponentData )

_randoms = new NativeArray<Random>(JobsUtility.MaxJobThreadCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
            var r = (uint) UnityEngine.Random.Range(int.MinValue, int.MaxValue);
            for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++)
                _randoms[i] = new Random(r == 0 ? r + 1 : r);
[BurstCompile]
        private struct SomeJob: IJobForEachWithEntity<SomeData>
        {
            [NativeDisableContainerSafetyRestriction]
            public NativeArray<Random> Randoms;
            [NativeSetThreadIndex] private int _threadId;

            public void Execute(Entity entity, int index, ref SomeData data)
            {
                var rnd      = Randoms[_threadId];
                //Do stuff
                Randoms[_threadId] = rnd;
            }
4 Likes

Why not use random.NextFloat() or its variants inside the job? Your solution has quite a bit of overhead due to populating that native array.

Thanks all of you. I did not know the Mathmatics library had a random function, and today I learned so much. Thank you, thank you, thank you.

You will use NextXXX on rnd.

Here fast example why “just” passing Random to job and using it wouldn’t be a “good” random.
Just run parallel job, and set batch size to 1 for splitting work to different threads. And here result of this “random” in every Execute, not so random huh? :slight_smile:

public class RandomSample : JobComponentSystem
{
    private struct PrintRandoms : IJobParallelFor
    {
        public Unity.Mathematics.Random random;

        public void Execute(int index)
        {
            Debug.Log(random.NextFloat(0, 1000f));
        }
    }

    private uint _seed = 999;
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        Debug.Log("----------START---------");
        new PrintRandoms()
        {
            random = new Unity.Mathematics.Random(_seed)
        }.Schedule(5, 1, inputDeps).Complete();
        Debug.Log("----------END---------");
        _seed++;
        return inputDeps;
    }
}

5191898--515972--upload_2019-11-19_20-9-12.png

4 Likes

Ahh wow very interesting. Thanks for clearing it up.

Well now in 2022.1.7f1 the test result above has changed.

[BurstCompile(DisableSafetyChecks = false)]
public struct TestRandomJob : IJobFor
    {
        Random rand;
        [WriteOnly] NativeArray<int> dst;

        public void Execute(int index)
        {
            // dst[index] = rand.NextInt() % 100;
            Debug.Log(rand.NextInt(100));
        }

        public static JobHandle ScheduleParallel(Random rand, NativeArray<int> dst)
        {
            var job = new TestRandomJob()
            {
                rand = rand,
                dst = dst
            };
            return job.ScheduleParallel(dst.Length, 1024, default);
        }
    }

Guess that there have been some interior code used to gen a unique rand for each Random field in the IJobFor struct for each Execute index.

What do you mean? Did you try eizenhorn’s code verbatim? They specifically use a batch size of 1 there.

The code you show specifies a batch size of 1024, which should mean at least that many are executed per worker batch, ergo you likely (for list size <= 1024) run every single iteration on one struct instance, and the Random instance itself would be mutated per iteration. If you’d have set the batch size to something lower, say in the range of 1 to 10, you’d see that many unique values repeated in the logs since each worker execution starts with the same (default) Random and subsequently produces a predictable value (as in it’s PRNG purely based off the generator state).

Were this not the case and Random fields are actually randomized outside user code… I would find that incredibly cursed.