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

Unity.Mathematics.Random and ECS

Discussion in 'Entity Component System' started by mr-gmg, Jan 24, 2020.

  1. mr-gmg

    mr-gmg

    Joined:
    Aug 31, 2015
    Posts:
    62
    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:
    Code (CSharp):
    1. using Unity.Mathematics;
    2. public class RandomWrapper {
    3.     public static RandomWrapper global;
    4.        
    5.     private Random m_random;
    6.  
    7.     public RandomWrapper(uint seed) {
    8.         m_random = new Random(seed);
    9.     }
    10.     public RandomWrapper(Random random) {
    11.         m_random = random;
    12.     }
    13.        
    14.     public int Range(int from, int to) {
    15.         return m_random.NextInt(from, to);
    16.     }
    17.     public float Range(float from, float to) {
    18.         return m_random.NextFloat(from, to);
    19.     }
    20.        
    21.     public ref Random GetRef() {
    22.         return ref m_random;
    23.     }
    24. }
    25.  
    Also, I have save/load in the game, which means I have to save and load the same random instance
    Code (CSharp):
    1. public void NewGame() {
    2.     SaveHelper.CleanUp();
    3.      //in game I create an entity with random data component to be able to store the random into this later
    4.     EntityManager.CreateEntity(typeof(RandomData));
    5.     //initialize wrapper with seed
    6.     RandomWrapper.global = new RandomWrapper(m_sessionSeed);
    7. }
    8. private void SaveGame(string fileName) {
    9.     //before saving the world, I have to store the random to the random data component
    10.     Entities.WithAll<RandomData>().ForEach((Entity entity, ref RandomData randomData) => {
    11.         randomData.random = RandomWrapper.global.GetRef();
    12.     });
    13.     SaveHelper.SaveGame(fileName);
    14. }
    15.  
    16. private void LoadGame(string fileName) {
    17.     SaveHelper.LoadGame(fileName);
    18.     //after loading the world I have to restore wrapper with the same random
    19.     Entities.WithAll<RandomData>().ForEach((Entity entity, ref RandomData randomData) => {
    20.         RandomWrapper.global = new RandomWrapper(randomData.random);
    21.     });
    22. }
    23.  
    24. protected override void OnCreate() {
    25.     base.OnCreate();
    26.     if (SaveHelper.IsQuickSaveExist()) {
    27.         LoadGame(SaveHelper.QuickSaveName);
    28.     } else {
    29.         NewGame();
    30.     }
    31. }
    32.  
    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
    Code (CSharp):
    1.  
    2. Entities.WithAll<RandomData>().ForEach((Entity randomEntity, ref RandomData randomData) => {
    3.     Entities.WithAll<PlayerData>().ForEach((Entity playerEntity, ref PlayerData playerData) => {
    4.         //but I can't use a ref randomData inside of lambda expression
    5.         randomData.random.NextFloat(playerData.from, playerData.to);
    6.     });
    7. });
    8.  
    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?
     
    Zagule likes this.
  2. colin_young

    colin_young

    Joined:
    Jun 1, 2017
    Posts:
    243
  3. mr-gmg

    mr-gmg

    Joined:
    Aug 31, 2015
    Posts:
    62
    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.
     
  4. colin_young

    colin_young

    Joined:
    Jun 1, 2017
    Posts:
    243
    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?
     
  5. mr-gmg

    mr-gmg

    Joined:
    Aug 31, 2015
    Posts:
    62
    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

    Code (CSharp):
    1. Entities.WithAll<RandomData>().ForEach((Entity entity, ref RandomData randomData) => {
    2.    int randomValue = randomData.random.Range(0, 100);
    3. });
    RandomData is just a math.Random wrapper
    Code (CSharp):
    1. [Serializable]
    2. public struct RandomData : IComponentData {
    3.     public Unity.Mathematics.Random random;
    4. }
     
  6. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    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.
     
  7. colin_young

    colin_young

    Joined:
    Jun 1, 2017
    Posts:
    243
    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:
    1. ensure the systems run in the same order, every time, no matter how many threads are available
    2. 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?
     
  8. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    699
    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).