About Me 👈

Random Number Generation with Unity DOTS

How to idiomatically generate random numbers in Burst-compilable jobs with Unity DOTS.

unitycsharptutorial

Created on January 1, 2020. Last updated on February 24, 2020.

Video of spawning prefabs with Unity ECS.

FYI, I made the core code for this tutorial into a UPM package, if you'd rather import it than follow everything here. 👀

Generating thread-safe random numbers with C# is not exactly straightforward. One option is to create [System.Random] objects that are [ThreadLocal]. That said, RNGCryptoServiceProvider is already thread-safe, so you could use it—but then you'd have to convert bytes to other primitive types, which is not too bad, but you'd also need some bit shifting to generate numbers within specified ranges, e.g. "I want an integer between 50 and 100."

Fortunately, there's Unity.Mathematics.Random at our disposal instead, a Xorshift-based random number generator. Not only is it performant, but its major advantage over alternatives is that it can be used with Burst-compiled jobs via the [BurstCompile] attribute or WithBurst.

Since your game may be highly dependent on (pseudo-)randomness, with various systems needing random numbers at any given time, it wouldn't make sense to allocate a new Unity.Mathematics.Random object every time a job executes. That's inefficient. Plus, figuring out how to seed each new object would be a challenge. Rather, we want one random number generation object per thread, available throughout the lifecycle of the game.

Framing these considerations in terms of ECS, all we really need is a single NativeArray of Unity.Mathematics.Random elements. For the uninitiated, this is a true array in the sense that it is ordered and memory-contiguous for efficiency of access. On startup, we'll create this publicly accessible array and seed its elements in RandomSystem, like so:

[UpdateInGroup(typeof(InitializationSystemGroup))]
class RandomSystem : ComponentSystem
{
    public NativeArray<Unity.Mathematics.Random> RandomArray { get; private set; }

    protected override void OnCreate()
    {
        var randomArray = new Unity.Mathematics.Random[JobsUtility.MaxJobThreadCount];
        var seed = new System.Random();

        for (int i = 0; i < JobsUtility.MaxJobThreadCount; ++i)
            randomArray[i] = new Unity.Mathematics.Random((uint)seed.Next());

        RandomArray = new NativeArray<Unity.Mathematics.Random>(randomArray, Allocator.Persistent);
    }

    protected override void OnDestroy()
        => RandomArray.Dispose();

    protected override void OnUpdate() { }
}

The gist of assigning the system to the InitializationSystemGroup is that we only want the system to run during the Initialization phase, which you can read more about in Unity's System Update Order docs.

And yeah, we're only using a ComponentSystem and not a JobComponentSystem since there are no jobs executed from here. And all we want to actually use are the OnCreate and OnDestroy hooks. In OnCreate, we seed the array elements with System.Random because, eh, why not? When the game's done, we dispose of (or deallocate) the NativeArray memory. The only blemish of this design is that we must override OnUpdate with an empty implementation. As far as the compiler is concerned, the worst-case scenario is that OnUpdate is called, but immediately relinquishes control to the caller.

Now, we're almost done! All you need to know is how to use this stuff with an arbitrary job system:

class SomeJobSystem : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var randomArray = World.GetExistingSystem<RandomSystem>().RandomArray;

        return Entities
            .WithNativeDisableParallelForRestriction(randomArray)
            .ForEach((int nativeThreadIndex, ref SomeComponent someComponent) =>
            {
                var random = RandomArray[nativeThreadIndex];

                someComponent.SomeField = random.Next(0, 1000);

                RandomArray[nativeThreadIndex] = random; // This is NECESSARY.
            })
            .WithName("SomeJob")
            .Schedule(inputDeps);
    }
}

We pass the array of random number generators to the Burst-compiled SomeJob, created through the Entities.ForEach syntactic sugar. Thread safety is accomplished by only modifying the state of each generator via the magic nativeThreadIndex (the current thread index of an executing job). Since we are managing safety ourselves, we must however pass the randomArray to WithNativeDisableParallelForRestriction. Yes, it's safe as long as you use the nativeThreadIndex as shown.

Note that, to ensure the state of a given generator updates upon each call to Execute, we must set it like so: RandomArray[nativeThreadIndex] = random. Otherwise, we'll only modify a copy of a given random generator object, and after a while things won't appear to be very random anymore!

And that's pretty much everything you need. FYI, the demos associated with my tutorials Projectile Motion with Unity DOTS and Spawning Prefabs with Unity ECS both use this pattern to generate random numbers. If you use my GitHub repo, which has working examples of all these tutorials, do me a solid and star it.