Search Unity

how to iterate an 2d array using jobsystem ?

Discussion in 'Entity Component System' started by Andreas88, Oct 21, 2018.

  1. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    i apologize beforehand if this question has been asked and answered before, but i really need some help here.
    my game use a grid system and i need to check different indexes in my grid, and sometimes iterate over my entire grid.
    at the moment i use the old objet oriented programming, since i am not yet ready to convert to ECS.

    So my question is, is there a way to use the job system for finding indexes in a 2d array using the Jobystem ?
    so far i have only been able to use a 1 dimensional array using nativecontainers.

    thank you and have a nice day :)
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    All dimensional arrays can be represented as 1d array (for 2d array for example: y*height + x)
     
  3. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    hi thanks for the quick reply. yes i know i could represent my 2d array as one giant array
    i just thought it would be such a hassle to convert it and all. and not to mention math heavy to find the correct index. i´m no math boy hehe.
    but thanks for the suggestion ;)
     
  4. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    451
    You consider finding an index of 2d array's cell in 1d representation as a math heavy? It is just one multiplication and one addition.
     
    shkar-noori likes this.
  5. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    hehe yeah i know but i wasn´t sure about how to use the equation.
    so, if i try to find the index of 2D array[x,y] inside 1D array. i should say index of array = Array [y*hight+x]
    is that what you mean ?
    also the height, is that the lenght of 2D array in y axis ?
    And any good tutorials on converting arrays back and forth in 2D/1D ?

    sorry for my dumb answer on the math part. i get very confused when someone gives me a math answer instead of showing me examples.
    thanks btw :)
     
  6. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Well yes.
    It's simplest math

    Here is an example:

    x x x x x
    x x x x x
    x x x x x

    This is an 5 x 3 2dim array. So you have 5 columns and 3 rows.
    The same coud be represented in 1 dim as follow (pipes are just for visualisation...)
    x x x x x | x x x x x | x x x x x


    Now you want to get the cell of x = 3 and y = 1 (the cell with "o")

    x x x x x
    x x x o x
    x x x x x

    a one dim array look like this
    x x x x x | x x x o x | x x x x x


    So you need to transfere the 2 dim index (x = 3 and y = 1) to an 1 dim index.
    Formular is just:
    index = y * number of columns + x
    Remember the first index of an array is 0!

    here is the table with it's 2d indeces :
    0;0 0;1 0;2 0;3 0;4
    1;0 1;1 1;2 1;3 1;4
    2;0 2;1 2;2 2;3 2;4

    here is the table with it's 1d indeces:
    0 1 2 3 4 => 0 * 5 + x
    5 6 7 8 9 => 1 * 5 + x
    10 11 12 13 15 => 2 * 5 + x

    a 1 dim array look like this
    0 1 2 3 4 | 5 6 7 8 9 | 10 11 12 13 15


    From 2d to 1d:
    i = y * width + x;
    i = 1 * 5 + 3 => 8


    From 1d to 2d: (all integer!!!)
    y = i / widht;
    x = i % widht;

    y = 8 / 5 => 1 because of integer we ignore the rest of the division the real value is 1.6 ...
    x = 8 % 5 => 3 we are interestet in the rest of the division => 8-1*5 = 3

    Hope this will help you a little bit.

    You have to know math if you want to be a programmer!
     
  7. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    451
    SPy-Shifty gave you a great example, but I think it would be better if you have tried to figure it out alone. If you want to be a programmer you need to be good at math and problem-solving. You can't learn everything from tutorials. You would learn much more If you have tried to figure it by yourself.
     
  8. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    hey man thanks for the explanation ;) i´m testing it out now and so far it all starts to make sense.

    "Micz84"
    usually i am a good problem solver and often i do solve my own problems. i have previously tried to convert an 2d array to 1d array. but i could not understand why it didn´t work and why i got some weird numbers out and stuff.

    the math part is something that often bite my butt, since i have always had a hard time to wrap my head around math. i do have decent math skills but often i try to solve my problems without using complicated math and such. everyone´s brain works differently, it does not matter if you write a few more lines of code instead of using some fancy math oriented approach that works in one line of code. the thing that matter is if the code performs what you asked for.
     
  9. Bytestorm83

    Bytestorm83

    Joined:
    Mar 17, 2018
    Posts:
    5
    So for a 2d <> 1d conversion, the above math is pretty simple to convert to a function.

    What you would do is place a function below the Execute() in the Job struct, which you can then call from within that job.

    I do not have an example of that for you, as it is indeed rather simplistic math that, if the job can use burst, it will complete very quickly.

    I do, however, have an example of what I use from a 3d <> 1d array to store voxel data. The advantage to this is that I don't need to store the position of the voxel in the data point, since its position in the array IS its position in World.

    Hope this provides a bit of insight into how this works jobified. As was said before, yours will be much simpler, and may not even need a defined function, since it really is just the math referenced above.


    Code (CSharp):
    1. struct mathTest : IJobParallelFor
    2.     {
    3.        [ReadOnly] public NativeList<float3> points;
    4.        [ReadOnly] public int mx;  //maximum value for x
    5.        [ReadOnly] public int my;  //maximum value for y
    6.        [ReadOnly] public int space; //spacing between each point
    7.        public void Execute(int i)
    8.        {
    9.            var test1 = getPos(i, mx, my, space);
    10.            var test2 = getInt(test1, mx, my, space);
    11.            Debug.LogFormat("Testing {0}: getPos({3}) = {1}, which getInt({1}) converts to {2}", points[i], test1, test2,i);
    12.        }
    13.        //mimics a for x, for y, for z nested loop
    14.        public float3 getPos(int i, int mx, int my, int space = 1)
    15.        {
    16.            int x = i / (mx * my);
    17.            int y = (int)math.mod((i / mx), my);
    18.            int z = (int)math.mod(i, mx);
    19.            return new float3(x * space, y * space, z * space);
    20.        }
    21.        public int getInt(float3 f, int mx, int my, int space = 1)
    22.        {
    23.            return (int)((f.z / space) + ((f.y * mx) / space) + (f.x * mx * my) / space);
    24.        }
    25.     }
     
  10. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    Hi Bytestorm83
    thank you very much for the very thorough example :)
    i have spend all day yesterday converting some code to 1D arrays and done some internal testing. and things work quite well :)
    today i´m working on a simple job. i am slowly starting to understand how things work, but so far i have got one error that i am pretty sure i undestand why is happening. the two variables called agent and squads are both a class type that contains all the data of my player units. and inside this class is an array that holds the positions of my actual units. The error i get is that, "agent" is not a value type.
    My theory is that i have to put all my "units" into a nativearray and then somehow convert it back to my original form, or am i missing something ?
    this is my code so far:
    Code (CSharp):
    1. public struct cohesion3 : IJobParallelFor
    2.     {
    3.      
    4.         public float deltatime;
    5.         public float speed;
    6.         public List< squad> agent;
    7.      
    8.  
    9.         public void Execute(int index)
    10.         {
    11.             if(agent[index].IsAlive)
    12.             {
    13.                 if (agent[index].Combat == false)
    14.                 {
    15.                     Vector2 left = new Vector2(agent[index].leader.x - 2, agent[index].leader.y);
    16.                     Vector2 right = new Vector2(agent[index].leader.x + 2, agent[index].leader.y);
    17.                     Vector2 up = new Vector2(agent[index].leader.x - 2, agent[index].leader.y + 2);
    18.                     Vector2 down = new Vector2(agent[index].leader.x + 2, agent[index].leader.y - 2);
    19.                     Vector2 center = new Vector2(agent[index].leader.x, agent[index].leader.y);
    20.                     float dt = deltatime;
    21.                     Vector2 velocity = Vector2.zero;
    22.                     float smoothing = speed;
    23.  
    24.                     agent[index].units[0] = Vector2.SmoothDamp(agent[index].units[0], left, ref velocity, smoothing);
    25.                     agent[index].units[1] = Vector2.SmoothDamp(agent[index].units[1], right, ref velocity, smoothing);
    26.                     agent[index].units[2] = Vector2.SmoothDamp(agent[index].units[2], up, ref velocity, smoothing);
    27.                     agent[index].units[3] = Vector2.SmoothDamp(agent[index].units[3], down, ref velocity, smoothing);
    28.                     agent[index].units[4] = Vector2.SmoothDamp(agent[index].units[4], center, ref velocity, smoothing);
    29.                 }
    30.             }
    31.             else
    32.             {
    33.                 return;
    34.             }
    35.        
    36.         }
    37.     }
    and in the update:
    Code (CSharp):
    1. cohesion3 CohesionJob = new cohesion3
    2.             {
    3.                 deltatime = dt,
    4.                 speed = CohesionSpeed,
    5.                 agent = squads
    6.              
    7.             };
    8.  
    9.             // schedule jobs
    10.             Cohesion_jobHandle = CohesionJob.Schedule(squads.Count, 32);
    11.             Cohesion_jobHandle.Complete();
     
  11. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    451
    Jobs and it's fields, elements stored in native containers and ECS components must be structs, you can't use classes.
     
  12. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Are you using ecs or just the Job system?

    Well, you can't use classes and arrays (reference types) inside a job!
     
  13. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    hey guys.
    well i´m still slowly learning how to use this stuff. i´m not doing any ECS, i´m just trying to jobify my existing code.
    i know it may not be the best option to store my data in classes, but for now i have to stick to classes and try to figure out a way to get all the data out and into my job.
    i have made some changes to my code, so far it likes my data structure but are now sending errors about not being able to dispose my nativearrays and some index out of range issues.
    Code (CSharp):
    1.  //initialze native container
    2.             var Leaderpos = new NativeArray<Vector2>(deactivatedindex, Allocator.Persistent);
    3.             var Unitpos = new NativeArray<Vector2>(5 * deactivatedindex, Allocator.Persistent);
    4.           //  var Alive = new NativeArray<bool>(deactivatedindex, Allocator.Persistent);
    5.           //  var CombatEnabled = new NativeArray<bool>(deactivatedindex, Allocator.Persistent);
    6.             int indexer = 0;
    7.             for (int i = 0; i < deactivatedindex; i++)
    8.             {
    9.                 Leaderpos[i] = squads[i].leader;
    10.            
    11.  
    12.                 for (int j = 0; j < squads[i].units.Length; j++)
    13.                 {
    14.                     Unitpos[i] = squads[i].units[i+indexer];
    15.                     indexer++;
    16.                 }
    17.              
    18.             }
    19.          
    20.  
    21.             //test job system for cohesion
    22.             //fill in the data
    23.             cohesion3 CohesionJob = new cohesion3
    24.             {
    25.                 deltatime = dt,
    26.                 speed = CohesionSpeed,
    27.                 Leader = Leaderpos,
    28.                 unit = Unitpos,
    29.            
    30.                
    31.             };
    32.  
    33.             // schedule jobs
    34.             Cohesion_jobHandle = CohesionJob.Schedule(deactivatedindex, 32);
    35.             Cohesion_jobHandle.Complete();
    36.  
    37.             Leaderpos.Dispose();
    38.             Unitpos.Dispose();
    39.          
    and the job struct:
    Code (CSharp):
    1.  //defining job structs
    2.     public struct cohesion3 : IJobParallelFor
    3.     {
    4.      
    5.         public float deltatime;
    6.         public float speed;
    7.         public NativeArray<Vector2> Leader;
    8.         public NativeArray<Vector2> unit;
    9.      //   public NativeArray<bool> IsAlive;
    10.        // public NativeArray<bool> Combat;
    11.  
    12.  
    13.         public void Execute(int index)
    14.         {
    15.            
    16.                
    17.                     Vector2 left = new Vector2(Leader[index].x - 2, Leader[index].y);
    18.                     Vector2 right = new Vector2(Leader[index].x + 2, Leader[index].y);
    19.                     Vector2 up = new Vector2(Leader[index].x - 2, Leader[index].y + 2);
    20.                     Vector2 down = new Vector2(Leader[index].x + 2, Leader[index].y - 2);
    21.                     Vector2 center = new Vector2(Leader[index].x, Leader[index].y);
    22.                     float dt = deltatime;
    23.                     Vector2 velocity = Vector2.zero;
    24.                     float smoothing = speed;
    25.                     int iterator = 0;
    26.                     for (int j = 0; j < unit.Length; j++)
    27.                     {
    28.                       //for testing
    29.                          unit[index + iterator] = center;
    30.                     }
    31.          
    32.         }
    33.     }
     
  14. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Your code is hard to read... please delete all unnecessary lines of code...

    First of all...
    Don't allocate you native array if you dispose it in the same frame. Use Temp instead. (It's faster)
    The Dealocation error will be thrown because of the index out of range error.

    So, if something went wrong and you won't catch it, the nativearray won't be disposed. It's because the code will never reach the dispose command...

    The next thing is:
    This won't work... You run through the hole unit array + the index...
    Code (CSharp):
    1.  for (int j = 0; j < unit.Length; j++)
    2.                     {
    3.                       //for testing
    4.                          unit[index + iterator] = center;
    5.                     }

    The index goes from 0 to deactivatedindex
    Iterator goes from 0 to unit.Length
    So you iterate from 0 to deactivatedindex+5*deactivatedindex

    You have:
    index+ j = unitIndex
    0+0 = 0
    0+1 = 1
    0+n = n

    1+0 = 1
    1+1 = 2
    1+n = 1+n

    n+0 = n
    n+1 = n+1
    n+n = 2n

    Thirst, this is your out of range error...
    Secondly, you overwrite the value you previouse set....

    the correct way woud be:
    Code (CSharp):
    1.  for (int j = 0; j < 5; j++)
    2.                     {
    3.                       //for testing
    4.                          unit[index*5 + iterator] = center;
    5.                     }
    because each Leaderpos has 5 units...


    By the way, as you wrote before:
    I totally disagree with that. For a beginner it's may be ok... But you should fast learn how to write clean code. It's more readable and maintainable!

    Anyway, go ahead you will learn it!
     
  15. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    first of all, thank you for the explanation on why my for loops didn´t work. second, i have spend all day adjusting my code and switched my class to be a struct. i have tried implementing my new code but now i got only one error: "ArgumentException: movment+squad used in NativeArray<movment+squad> must be blittable"

    after searching for answer i think it´s because i use boolean variables inside my squad struct, or am i wrong ?
    here is the modified code:
    Code (CSharp):
    1. var units = new NativeArray<squad>(deactivatedindex, Allocator.Persistent);
    2.          
    3.             int indexer = 0;
    4.             for (int i = 0; i < deactivatedindex; i++)
    5.             {
    6.  
    7.                 units[i] = squads[i];
    8.              
    9.             }
    10.  
    11.  
    12.             //test job system for cohesion
    13.             //fill in the data
    14.             cohesion3 CohesionJob = new cohesion3
    15.             {
    16.                 deltatime = dt,
    17.                 speed = CohesionSpeed,
    18.                  agent = units,
    19.            
    20.             };
    21.  
    22.             // schedule jobs
    23.             Cohesion_jobHandle = CohesionJob.Schedule(deactivatedindex, 32);
    24.             Cohesion_jobHandle.Complete();
    25.  
    26.            
    27.             units.Dispose();
    the job struct.
    Code (CSharp):
    1.  public struct cohesion3 : IJobParallelFor
    2.     {
    3.      
    4.         public float deltatime;
    5.         public float speed;
    6.         public NativeArray<squad> agent;
    7.  
    8.         public void Execute(int index)
    9.         {
    10.            
    11.                
    12.                     Vector2 left = new Vector2(agent[index].leader.x - 2, agent[index].leader.y);
    13.                     Vector2 right = new Vector2(agent[index].leader.x + 2, agent[index].leader.y);
    14.                     Vector2 up = new Vector2(agent[index].leader.x - 2, agent[index].leader.y + 2);
    15.                     Vector2 down = new Vector2(agent[index].leader.x + 2, agent[index].leader.y - 2);
    16.                     Vector2 center = new Vector2(agent[index].leader.x, agent[index].leader.y);
    17.                     float dt = deltatime;
    18.                     Vector2 velocity = Vector2.zero;
    19.                     float smoothing = speed;
    20.                     int iterator = 0;
    21. //do something
    22.  
    23.  
    24.         }
    25.     }
     
  16. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Yep boolean isn't blittable:
    https://en.wikipedia.org/wiki/Blittable_types

    here is a workaround:

    Code (CSharp):
    1. [Serializable]
    2. public struct boolean {
    3.     [SerializeField] private byte boolValue;
    4.  
    5.     public boolean(bool value) {
    6.         boolValue = (byte)(value ? 1 : 0);
    7.     }
    8.     public static implicit operator bool(boolean value) {
    9.         return value.boolValue == 1;
    10.     }
    11.     public static implicit operator boolean(bool value) {
    12.         return new boolean(value);
    13.     }
    14.  
    15.     public override string ToString() {
    16.         return ((bool)this).ToString();
    17.     }
    18. }
     
  17. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    hi spy-shifty
    thank you for the bool workaround. i think it may be easier just treating my booleans as int for convenient sake. will get back if i still got issues ;)
    thanks
     
  18. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    hey everyone, just thought i would share some insight with you guys.
    after converting my bools to int i still got the none blittable error. after doing some digging, i learned that my job didn´t allow me to have arrays or lists inside my struct.
    does anyone know any other type of array/list i can use inside my struct ? i even tried nativearray but that didn´t work.
     
  19. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    No one of them. You must use Dynamic Buffers for arrays logic
     
  20. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    oh, damn. dynamic buffers are only for ECS, right ?
     
  21. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    So i have been thinking, it seems like the more i try to avoid using ECS with the jobsystem. the more issues i run into, maybe i am shooting myself in my foot for not using ECS.
    As it is now, i can successfully get my struct into the job, but with some missing key elements. but for some reason i am unable to modify my struct variables in my job.
    if i add other public variables to my job, i can easily modify those but the nativearray won´t be modified.
    I tried to follow the space ship tutorial on unity website, and that part works very well but when i apply it to my struct it won´t work, have no idea why. No errors it just doesen´t modify my struct.

    Code (CSharp):
    1.  public struct cohesion3 : IJobParallelForTransform
    2.     {
    3.  
    4.         public float deltatime;
    5.         public float speed;
    6.         public int test;
    7.        
    8.         public NativeArray<squad> agent;
    9.         public NativeArray<int> A;
    10.     public squad unit;
    11.    
    12.         public void Execute(int index,TransformAccess transform)
    13.         {
    14.             Vector3 pos = transform.position;
    15.          
    16.             unit.leader += speed * deltatime * Vector3.forward; //trying to move unit.leader forward
    17.            
    18.         pos += speed * deltatime * (transform.rotation * new Vector3(1f,0f,0f)); // this is from a tutorial, does the same as the above and works
    19.  
    20.             A[index] = A[index] +test; // A gets kinda modified but not correctly
    21.  
    22.             test += 1; //test works but only gets added 5 times
    23.          
    24.             agent[index] = unit; //no change
    25.            
    26.         transform.position = pos; // transform gets moved every frame, this works
    27.          
    28.         }
    29.     }
     
  22. Bytestorm83

    Bytestorm83

    Joined:
    Mar 17, 2018
    Posts:
    5
    Perhaps you should take a step back, and re-evaluate your decision.

    A mild examination of your code would indicate that what you are trying to do is very much what ECS is designed for.

    I know, it sucks having to nigh completely rewrite your code, but it will be ultimately beneficial.

    If you do, your Entity would be a Squad (data components, no rendering), with a Buffer of other Entities (members of the squad, MeshInstanceRenderer Component). You would then create:
    1. a job that moves the Squad position x amount a frame
    2. a job that runs on the Members, moving them to the Squad position every frame
    This allows the most versatility from a coding perspective.

    There are other ways to do this, of course. You could have the Entities being only the Soldiers, which have your own custom Components, Squad, SquadPosition, etc. This way you can use the Entity Filtering options to target specifically who you want to move, and keep them at their positions relative to the SquadPosition. This might be more optimal, depending on your end goal.

    This is just my 2 cents.
     
  23. Andreas88

    Andreas88

    Joined:
    Dec 22, 2015
    Posts:
    67
    hey thanks man.
    yeah, so far i have been trying to implement my own kind of data oriented approach. i am very excited about the change to ECS, but i have just been very reluctant to make the change. mostly because i didn´t feel the ECS was ready yet and there are still lots of new stuff i need to learn before i can use it safely. it has always been my intention to move to ECS at some point.
    do you guys know where to look up the ECS API ? or perhaps some good step by step tutorials ?
    also, out of curiosity, would it make sense to make an archetype for each kind of unit ? also i would very much like to know more about dynamic buffers.
    thanks