Unity.Mathematics.Random and ECS

Hello guys, I’m trying to find a good solution on how to use Mathematics.Random and ECS correctly.
I have a randomly generated world with a world seed, which means every randomly generated value in this game has to work with the same random struct instance.

My solution that I came up with, I did a simple random wrapper:

using Unity.Mathematics;
public class RandomWrapper {
    public static RandomWrapper global;
       
    private Random m_random;

    public RandomWrapper(uint seed) {
        m_random = new Random(seed);
    }
    public RandomWrapper(Random random) {
        m_random = random;
    }
       
    public int Range(int from, int to) {
        return m_random.NextInt(from, to);
    }
    public float Range(float from, float to) {
        return m_random.NextFloat(from, to);
    }
       
    public ref Random GetRef() {
        return ref m_random;
    }
}

Also, I have save/load in the game, which means I have to save and load the same random instance

public void NewGame() {
    SaveHelper.CleanUp();
     //in game I create an entity with random data component to be able to store the random into this later
    EntityManager.CreateEntity(typeof(RandomData));
    //initialize wrapper with seed
    RandomWrapper.global = new RandomWrapper(m_sessionSeed);
}
private void SaveGame(string fileName) {
    //before saving the world, I have to store the random to the random data component
    Entities.WithAll<RandomData>().ForEach((Entity entity, ref RandomData randomData) => {
        randomData.random = RandomWrapper.global.GetRef();
    });
    SaveHelper.SaveGame(fileName);
}

private void LoadGame(string fileName) {
    SaveHelper.LoadGame(fileName);
    //after loading the world I have to restore wrapper with the same random
    Entities.WithAll<RandomData>().ForEach((Entity entity, ref RandomData randomData) => {
        RandomWrapper.global = new RandomWrapper(randomData.random);
    });
}

protected override void OnCreate() {
    base.OnCreate();
    if (SaveHelper.IsQuickSaveExist()) {
        LoadGame(SaveHelper.QuickSaveName);
    } else {
        NewGame();
    }
}

when I need to get a random value I use RandomWrapper.global.Range(0, 100) and it changes the state of random

And this actually works, but I feel like I did something wrong, and it is not the ECS way how to use random correctly.
I can’t imagine how to use it differently because if in the system I need a random value related to other components it should look like that

Entities.WithAll<RandomData>().ForEach((Entity randomEntity, ref RandomData randomData) => {
    Entities.WithAll<PlayerData>().ForEach((Entity playerEntity, ref PlayerData playerData) => {
        //but I can't use a ref randomData inside of lambda expression
        randomData.random.NextFloat(playerData.from, playerData.to);
    });
});

But I cannot do that because I have to copy the random struct and work with the copy, which breaks random state
So, how would you implement this?

1 Like

This is the post I used to figure out how to do random with jobs: [SOLVED] Gen random number in Job Execute causes hang

yeah, thank you, I did not do it so far, but I think I understand how to use math random in jobs. My question is a little bit different, I’m curious about the practice how to use in ECS the single instance of math random in a number of independent systems.

Could you explain why you need to use a single instance of random in multiple systems? What do you mean by “random value related to other components”? Are you simply attempting to ensure that the same sequence of “random” numbers is generated every time, i.e. so you can regenerate the same world again?

Thank you for your questions, while I answered, I understand that probably my questions are incorrect. I will try to explain what I want to do, and probably you will explain, how I may achieve this. What I wrote below is not exactly what I want to do, but it will help me to understand how to organize all of this.

I want to create and simulate a world by seed, and everything in this simulation should be the same during each simulation with the same seed.

For example, I want to create N random star systems, every system has N random planets, a planet may randomly have a colony, every planet with a colony have N random ships for trading, protection, etc. Those ships may randomly decide what is the next action, should I go to planet A or to planet B.

Let start step by step, where I have to store my random? It is should be on a new randomEntity, and I have to iterate through Entities every time when I want to get an access to that random?
for example

Entities.WithAll<RandomData>().ForEach((Entity entity, ref RandomData randomData) => {
   int randomValue = randomData.random.Range(0, 100);
});

RandomData is just a math.Random wrapper

[Serializable]
public struct RandomData : IComponentData {
    public Unity.Mathematics.Random random;
}

It sounds like you don’t want anything random at all. You want it to run and stay the same, then you probably want a procedural system. I dont know much about that, but I know its not random at all. It can look random though. My guess for one simple way you could do it is you could create an algorithm that uses the transform position of a given solar system to create different numbers of planets at each position.

I don’t think the first step is figuring out where to store a random number generator. I also don’t think there is any need to share that generator.

When you say everything should be the same starting from the same seed, is that running on the same hardware, or should the outcome be identical no matter what hardware it runs on? In the latter case, you will need to ensure all your systems run in a deterministic order no matter how many threads are available to run on. You can achieve that by:

  • ensure the systems run in the same order, every time, no matter how many threads are available
  • use the same stream of random numbers every run

You can achieve #2 either by using a single stream of random numbers using the same seed, or by having each system create its own stream of random numbers with the same seed (which could come from the root random sequence which is generating the same sequence each run) each run.

IMHO, the bigger problem is managing the runs so that each system runs in the same sequence, over the same entities, in the same order, every single run. Sharing the random number generator isn’t going to solve that problem.

I’m also a bit curious why you want to be able to re-run to get the same results rather than just saving progress. Is this going to be a distributed game, so that you want independent systems to be able to get to the same end-state without needing excessive network communication?

Unity.Mathematics.Random is basically a single uint, and some code.

It can totally go into a IComponentData.

For a game that needs reproducable values, you need to ensure that each actor will draw from a controlled random source in a deterministic order.

A multi-threaded system cannot safely do this without a sync point; or by having each individual consumer/producer manage their own random state, presuming it is never codependent. (otherwise… you need those hard sync points).