Search Unity

reusing functions inside jobs

Discussion in 'Entity Component System' started by Robber33, Nov 23, 2018.

  1. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    how would i define a function that i can use in different jobs ?

    for example i have an algorithm that calculates the height of a terrain i'm generating.
    so i use this for example for calculating vertex positions of a mesh(tile).

    now i would like to use the same algorithm to get the height on a specific position, for collision checking.

    is it possible to define a static ? height() function that i can use in different jobs ?
    what if this function requires a lookup table, like a perlin hash table.

    right now i pass the lookuptable as a nativearray to a job and do all the calculation in the job without a sub-function. but if i could reuse the algorith it would make things easier than copying the same code to each job.

    thanx for any suggestions,
    Rob
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,776
    Yes and yes.
    But if you can for example define system, which calculates height and access with accordingly tagged entities ,rather than static method, I think would be better. Depends on application.
     
    Robber33 likes this.
  3. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    I'm not using entities, just the jobsystem,
    basically i would just need a helper function that i pass some parameters and returns a height value.

    so i would get the height for a big array of position to create mesh in an iparalelfor job, or just get the height for single position in a ijob if i want to check where the floor is for example
     
  4. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    You can create a static method which doesn't use and refer to any objects or managed arrays if you want to benefit from [BurstCompiler]. You will have to pass every value or e.g. nativearray you want to process.

    For convience, I always create a non-static / object variant of the method which basically wraps the static method. I use that instantiated method wrapper for on demand / direct calls in normal methods (not inside jobs) so I don't have to pass all arguments over and over again.
     
    Robber33 likes this.
  5. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    thanx for the suggestions, I have it working also with a static readonly lookup table.

    @meanmonkey : could you elaborate a bit on your technique for wrapping the static function ? I have for example an animationcurve that i sample into 512 values and pass to my jobs as a native array, but it would be convenient if i would not have to pass all argument all the time as you suggest ?

    thanx!
     
  6. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    Sure:

    Code (CSharp):
    1.  public class SomeClass
    2.         {
    3.             public NativeArray<int> array;
    4.             public int value1;
    5.             public int value2;
    6.             public int value3;
    7.  
    8.             public SomeClass()
    9.             {
    10.              
    11.             }
    12.  
    13.             public int GetData()
    14.             {
    15.                 return GetData_Static(value1, value2, value3, array);
    16.             }
    17.  
    18.             public static int GetData_Static(int _value1, int _value2, int _value3, NativeArray<int> _array)
    19.             {
    20.                 return _array[_value1 + _value2 + _value3];
    21.             }
    22.         }
    You can use GetData_Static(args) in your jobs and GetData() for convinience.

    An alternative would be using static fields or a static class at all, but the burst compiler will only be happy if static fields are readonly. But as readonly fields in static classes have to be initialized in the static constructor, you can't init your data on demand at object creation which can be pretty unconvinient.
     
    Last edited: Nov 23, 2018
  7. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    i see, thank you...!
     
  8. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    job performance has completely dropped through the floor, is this because of the static lookup tables ?
    how would i use a static getSample function as below in a IJobParallelFor job ?

    Code (CSharp):
    1. public struct HeightFunctions {
    2.     public readonly static int[] height_hash = {
    3. 151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233,  7,225,140, 36,103, 30, 69,142,  8, 99, 37,240, 21, 10, 23,190,  6,148, 247,120,234, 75,  0, 26,197, 62, 94,252,219,203,117, 35, 11, 32, 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175, 74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122, 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54, 65, 25, 63,161,  1,216, 80, 73,209, 76,132,187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186,  3, 64, 52,217,226,250,124,123,  5,202, 38,147,118,126,255, 82, 85,212,207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213, 119,248,152,  2, 44,154,163, 70,221,153,101,155,167, 43,172,  9,129, 22, 39,253, 19, 98,108,110, 79,113,224,232,178,185,112,104, 218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157, 184, 84,204,176,115,121, 50, 45,127,  4,150,254,138,236,205, 93,222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180
    4.     };
    5.     public readonly static int height_hash_mask = 255;
    6.  
    7.     public readonly static Vector2[] gradients2D = {
    8.         new Vector2( 1f, 0f), new Vector2(-1f, 0f),
    9.         new Vector2( 0f, 1f), new Vector2( 0f,-1f),
    10.         new Vector2( 1f, 1f).normalized, new Vector2(-1f, 1f).normalized,
    11.         new Vector2( 1f,-1f).normalized, new Vector2(-1f,-1f).normalized
    12.     };
    13.     public readonly static int gradientsMask2D = 7;
    14.  
    15.     public static float getSample(float xf, float yf, int xi, int yi) {
    16.         float value = 0f;
    17.         float unskew = (xi + yi) * 0.2113249f; // * squaresToTriangles;
    18.         float xn = xf - xi + unskew;
    19.         float yn = yf - yi + unskew;
    20.  
    21.         float f = 0.5f - xn*xn - yn*yn;
    22.         if (f > 0f) {
    23.             float f2 = f * f;
    24.             float f3 = f * f2;
    25.             Vector2 g = gradients2D[height_hash[height_hash[xi & height_hash_mask] + yi & height_hash_mask] & gradientsMask2D];
    26.             float v = g.x* xn + g.y * yn;
    27.             value = v * f3;
    28.         }
    29.         return value;
    30.     }
    31. }
     
  9. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    Replace your managed arrays to nativearrays and create a IJobParallelFor and tag it with [BurstCompile] (note that native arrays + burst is only efficient in a build, not in editor).

    something like this (just hacked it down so it can be error prone):

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Jobs;
    3. using UnityEngine.Jobs;
    4. using Unity.Collections;
    5. using Unity.Entities;
    6. using Unity.Burst;
    7.  
    8. // =================================================================== //
    9. public class ParallelForJobTest {
    10.  
    11.     public readonly NativeArray<int> height_hash;
    12.     public readonly int height_hash_mask;
    13.  
    14.     public readonly NativeArray<Vector2> gradients2D;
    15.     public readonly int gradientsMask2D;
    16.  
    17.     public NativeArray<float> results;
    18.  
    19.     // ------------------------------------------------------------------- //
    20.     public ParallelForJobTest() {
    21.  
    22.         height_hash = new NativeArray<int>( new int[] {
    23.             151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233,  7,225,140, 36,103, 30, 69,142,  8, 99, 37,240, 21, 10, 23,190,  6,148, 247,120,234, 75,  0, 26,197, 62, 94,252,219,203,117, 35, 11, 32, 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175, 74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122, 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54, 65, 25, 63,161,  1,216, 80, 73,209, 76,132,187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186,  3, 64, 52,217,226,250,124,123,  5,202, 38,147,118,126,255, 82, 85,212,207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213, 119,248,152,  2, 44,154,163, 70,221,153,101,155,167, 43,172,  9,129, 22, 39,253, 19, 98,108,110, 79,113,224,232,178,185,112,104, 218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157, 184, 84,204,176,115,121, 50, 45,127,  4,150,254,138,236,205, 93,222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180
    24.         }, Allocator.Persistent);
    25.  
    26.         height_hash_mask = 255;
    27.  
    28.         gradients2D = new NativeArray<Vector2>(new Vector2[]  {
    29.         new Vector2( 1f, 0f), new Vector2(-1f, 0f),
    30.         new Vector2( 0f, 1f), new Vector2( 0f,-1f),
    31.         new Vector2( 1f, 1f).normalized, new Vector2(-1f, 1f).normalized,
    32.         new Vector2( 1f,-1f).normalized, new Vector2(-1f,-1f).normalized
    33.         }, Allocator.Persistent);
    34.  
    35.         gradientsMask2D = 7;
    36.  
    37.         int myJobSize = 1000; // ??
    38.         results = new NativeArray<float>(myJobSize, Allocator.Persistent);
    39.  
    40.         TestJob testJob = new TestJob()
    41.         {
    42.             _height_hash = height_hash,
    43.             _height_hash_mask = height_hash_mask,
    44.             _gradients2D = gradients2D,
    45.             _gradientsMask2D = gradientsMask2D
    46.  
    47.  
    48.         };
    49.         JobHandle testJobHandle = testJob.Schedule(myJobSize, 1); // choose your batchcount wisely. the more work has to be done, the less batches you wann do per thread.
    50.         testJobHandle.Complete();
    51.  
    52.      
    53.     }
    54.    
    55.     // ------------------------------------------------------------------- //
    56.     public void ShutDown()
    57.     {
    58.         height_hash.Dispose();
    59.         gradients2D.Dispose();
    60.     }
    61.  
    62.     // =================================================================== //
    63.     [BurstCompile]
    64.     struct TestJob : IJobParallelFor
    65.     {
    66.         //[NativeDisableParallelForRestriction] // Use this on nativearrays if you want to read/write non linear. You have to guarantee thread saftey by yourself!
    67.  
    68.         [ReadOnly]
    69.         public NativeArray<int> _height_hash;
    70.         public int _height_hash_mask;
    71.  
    72.         [ReadOnly]
    73.         public NativeArray<Vector2> _gradients2D;
    74.         public int _gradientsMask2D;
    75.      
    76.         [WriteOnly]
    77.         public NativeArray<float> results;
    78.        
    79.         // ------------------------------------------------------------------- //
    80.         public void Execute(int i)
    81.         {
    82.             float xf; float yf; int xi; int yi;  // I guess you want to use these in the batch, dunno your logic.
    83.  
    84.             float value = 0f;
    85.             float unskew = (xi + yi) * 0.2113249f; // * squaresToTriangles;
    86.             float xn = xf - xi + unskew;
    87.             float yn = yf - yi + unskew;
    88.  
    89.             float f = 0.5f - xn * xn - yn * yn;
    90.             if (f > 0f)
    91.             {
    92.                 float f2 = f * f;
    93.                 float f3 = f * f2;
    94.                 Vector2 g = _gradients2D[_height_hash[_height_hash[xi & _height_hash_mask] + yi & _height_hash_mask] & _gradientsMask2D];
    95.                 float v = g.x * xn + g.y * yn;
    96.                 value = v * f3;
    97.             }
    98.  
    99.             results[i] = value;
    100.         }
    101.     }
    102. }
     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    It's efficient everywhere, even in editor, bursted job - performant in several times, in build this values just much more, because all editor safety checks excluded from build.
     
    Last edited: Nov 23, 2018
  11. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    Thanx! Yes this was exactly how i was using the jobsystem, but i was wondering if i could make some functions,
    so i could easily reuse the algorithm in different types jobs that need the same calculations, in stead of having each job contain just a copy of the same code.
     
  12. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    you mean like this ?

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Jobs;
    3. using UnityEngine.Jobs;
    4. using Unity.Collections;
    5. using Unity.Entities;
    6. using Unity.Burst;
    7.  
    8. // =================================================================== //
    9. public class ParallelForJobTest {
    10.  
    11.     public readonly NativeArray<int> height_hash;
    12.     public readonly int height_hash_mask;
    13.  
    14.     public readonly NativeArray<Vector2> gradients2D;
    15.     public readonly int gradientsMask2D;
    16.  
    17.     public NativeArray<float> results;
    18.  
    19.     // ------------------------------------------------------------------- //
    20.     public ParallelForJobTest() {
    21.  
    22.         height_hash = new NativeArray<int>( new int[] {
    23.             151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233,  7,225,140, 36,103, 30, 69,142,  8, 99, 37,240, 21, 10, 23,190,  6,148, 247,120,234, 75,  0, 26,197, 62, 94,252,219,203,117, 35, 11, 32, 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175, 74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122, 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54, 65, 25, 63,161,  1,216, 80, 73,209, 76,132,187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186,  3, 64, 52,217,226,250,124,123,  5,202, 38,147,118,126,255, 82, 85,212,207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213, 119,248,152,  2, 44,154,163, 70,221,153,101,155,167, 43,172,  9,129, 22, 39,253, 19, 98,108,110, 79,113,224,232,178,185,112,104, 218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157, 184, 84,204,176,115,121, 50, 45,127,  4,150,254,138,236,205, 93,222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180
    24.         }, Allocator.Persistent);
    25.  
    26.         height_hash_mask = 255;
    27.  
    28.         gradients2D = new NativeArray<Vector2>(new Vector2[]  {
    29.         new Vector2( 1f, 0f), new Vector2(-1f, 0f),
    30.         new Vector2( 0f, 1f), new Vector2( 0f,-1f),
    31.         new Vector2( 1f, 1f).normalized, new Vector2(-1f, 1f).normalized,
    32.         new Vector2( 1f,-1f).normalized, new Vector2(-1f,-1f).normalized
    33.         }, Allocator.Persistent);
    34.  
    35.         gradientsMask2D = 7;
    36.  
    37.         int myJobSize = 1000; // ??
    38.         results = new NativeArray<float>(myJobSize, Allocator.Persistent);
    39.  
    40.         TestJob testJob = new TestJob()
    41.         {
    42.             _height_hash = height_hash,
    43.             _height_hash_mask = height_hash_mask,
    44.             _gradients2D = gradients2D,
    45.             _gradientsMask2D = gradientsMask2D
    46.  
    47.  
    48.         };
    49.         JobHandle testJobHandle = testJob.Schedule(myJobSize, 1); // choose your batchcount wisely. the more work has to be done, the less batches you wann do per thread.
    50.         testJobHandle.Complete();
    51.  
    52.      
    53.     }
    54.  
    55.     // ------------------------------------------------------------------- //
    56.     public void ShutDown()
    57.     {
    58.         height_hash.Dispose();
    59.         gradients2D.Dispose();
    60.     }
    61.  
    62.     // =================================================================== //
    63.     [BurstCompile]
    64.     struct TestJob : IJobParallelFor
    65.     {
    66.         //[NativeDisableParallelForRestriction] // Use this on nativearrays if you want to read/write non linear. You have to guarantee thread saftey by yourself!
    67.  
    68.         [ReadOnly]
    69.         public NativeArray<int> _height_hash;
    70.         public int _height_hash_mask;
    71.  
    72.         [ReadOnly]
    73.         public NativeArray<Vector2> _gradients2D;
    74.         public int _gradientsMask2D;
    75.      
    76.         [WriteOnly]
    77.         public NativeArray<float> results;
    78.      
    79.         // ------------------------------------------------------------------- //
    80.         public void Execute(int i)
    81.         {
    82.             float xf; float yf; int xi; int yi; // I guess you want to use these in the batch, dunno your logic.
    83.  
    84.             results[i] = GetSampe(xf, yf, xi, yi, _height_hash, _height_hash_mask, _gradients2D,);
    85.         }
    86.     }
    87.  
    88.     // ------------------------------------------------------------------- //
    89.     public static float GetSampe(float xf, float yf, int xi, int yi, NativeArray<int> _height_hash, int _height_hash_mask, NativeArray<Vector2> _gradients2D, int _gradientsMask2D)
    90.     {
    91.  
    92.         float value = 0f;
    93.         float unskew = (xi + yi) * 0.2113249f; // * squaresToTriangles;
    94.         float xn = xf - xi + unskew;
    95.         float yn = yf - yi + unskew;
    96.  
    97.         float f = 0.5f - xn * xn - yn * yn;
    98.         if (f > 0f)
    99.         {
    100.             float f2 = f * f;
    101.             float f3 = f * f2;
    102.             Vector2 g = _gradients2D[_height_hash[_height_hash[xi & _height_hash_mask] + yi & _height_hash_mask] & _gradientsMask2D];
    103.             float v = g.x * xn + g.y * yn;
    104.             value = v * f3;
    105.         }
    106.  
    107.         return value;
    108.     }
    109. }
    110.  
     
    Robber33 likes this.
  13. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    exactly yes..thank you, i guess i have to really separate all data from the static functions.
    although a single float or int is ok, i cannot declare a small array as a lookuptable in this static function ?
     
  14. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    Not sure but I think you can't declare arrays inside a job when using burst. You'll have to try, but the burst debugger will tell you if he's not happy :)
     
  15. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    ok yes, my jobs were going from 2ms to 200ms :/
    but i understand it a bit better and i can clean up / reuse my code a bit now with some functions,
    as long as i pass in all the data it needs, thanx for your help.
     
  16. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,776
    Not sure if that helps, but maybe try replace Vector2 with float2.

    I think problem with static method in your case may be, you are copying NativeArray to it, on each iteration, when accessing method.
    Depends how big arrays are, this may cause some potential hiccups.
    But I may be wrong for your case.

    /\ This

    Ignore the fact, it is ECS.
    No burst
    View attachment 333718

    With burst
    View attachment 333721
     
    CodeSmile likes this.
  17. Robber33

    Robber33

    Joined:
    Feb 22, 2015
    Posts:
    52
    performance is the same for me with static function as with direct inlining the code, i dont think there is a copy made for nativearray
     
  18. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    from
    from 2 to 200ms, that sounds wrong somehow. But I dunno what you are doing else in your code and what your start/stop point of time measure is. Also note that nativearrays and jobs are much slower in the editor due to safety checks which won't occur in the final build. https://forum.unity.com/threads/is-nativearray-slower-than-regular-array-2018-1-0b6.518137/

    The best performance you get with nativearrays is always combined with burst jobs. Also note that native arrays outside burst jobs + mono are slower than managed arrays + mono, but you can use il2cpp if feasable for you, this will compensate the performance loss. https://forum.unity.com/threads/job-system-not-as-fast-as-mine-why-not.518374/#post-3397587
     
    Robber33 likes this.
  19. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,945
    Im reviving this thread as i have the same Question. I want to reuse code between Jobs.
    So for example i have seperate jobs for
    - Get Populated Area Job from 2D Grid
    - Get Indeces of Items from Native List Job
    - and so on.

    Now in one case i need all these functionality in one job instead of creating all these smaller jobs in order.

    So i want to use static/extension methods which will work on Native Containers or blittable types (int, bool) etc to reuse code between jobs.


    So is this still the right Strategy in end of 2019? or is there a better approach?
    Thanks
     
  20. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    jGate99 likes this.
  21. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,945
    siggigg likes this.
  22. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    That looks interesting, but can someone explain me the use case of this? I read through it several times but I don't really get it. I already can access static functions (which are burst compiled too, then ?) from within burst jobs. Thank you
     
  23. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,970
    Maybe it worked way back when, but with current Burst 1.7 / 1.8 you can't do this:

    Code (CSharp):
    1. [BurstCompile]
    2. public static void SetVert(in Vertex v, NativeList<int> verts, int index) => verts[index] = v.Index;
    3.  
    The error is:

    Neither in, ref nor out modifiers can be used to pass a struct. No native collections can be passed to a static method since they are structs. Note that it's not complaining about the Vertex struct, the error occurs even in methods with just a native collection as parameter. I haven't tried pointers yet, nor function pointers - will investigate those tomorrow.

    I post this because I'm also investigating how to re-use methods across jobs, all of which need access to the same four NativeList. I don't mind passing the lists to every job but I can't just copy/paste every utility function I need into every job, that would be madness. If anyone knows the modern best practice for that please let us know!

    PS: If there is any doubt about Burst + Collection benefits in Editor, I can tell you I made one giant IJob (just this one!) running in editor that made this complex code run in 1 ms where it used to take 100 ms without Burst / Jobs. I made only minor modifications when porting since it was built Jobs-compatible to begin with, so the code essentially stayed the same only that it now benefits from Burst compilation.
     
    Last edited: Aug 25, 2022
  24. Trindenberg

    Trindenberg

    Joined:
    Dec 3, 2017
    Posts:
    398
    Would this work?

    public static void SetVert(in Vertex v, ref NativeList<int> verts, int index) => ref verts[index] = v.Index;
     
  25. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,970
    Nope. You cannot use in or ref.
    And the last ref is not valid syntax (in Unity).
     
    Last edited: Aug 25, 2022
  26. Trindenberg

    Trindenberg

    Joined:
    Dec 3, 2017
    Posts:
    398
    Not sure at what point the error shows (I haven't been on Unity for a while). So would it accept:


    1. [BurstCompile]
    2. public unsafe static void SetVert(in Vertex v, NativeList<int>* verts, int index) => verts[index] = v.Index;
     
    Last edited: Aug 29, 2022
  27. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,970
    That is what I want to try today: pointers.

    In the meantime I‘ve ran across another potentially better solution: for each reusable module, make a struct and put the methods and eventual fields in it. Add that struct to any job that needs that module.
     
  28. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    NativeArray only store the pointer to the data not the data itself, you never copy the entire array if you pass it as an argument. Or did I miss a copy operation?
     
  29. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,970
    I suppose the NativeArray fields are copied, so you get "another copy of the pointer to the data" and anything else NativeArray may store in fields - but the storage keeps pointing to the same native memory location.

    I know this because I wasn't 100% sure so yesterday I made a job, assigned my class array field to it, and after the job is done I do not copy anything from that array out - the array field in my class gets updated by the job directly. That is sweet!
     
  30. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,776
    To be honest, this is reply to my 4 y old post :) I was trying point out, what may went wrong in terms of performance, in OP case.

    But DOTS and my knowledge were much different back then :)

    Today I use IN and REF keys in method, when passing native collections, so it is clear, what the purpose of the collection is.

    Besides, yes NativeArray will be passed by pointer to data.
     
  31. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    The forum didn't jump to the latest post, then I forgot to look at the date :oops:
     
    Antypodish, CodeSmile and Anthiese like this.
  32. Goularou

    Goularou

    Joined:
    Oct 19, 2018
    Posts:
    54
    Passing a Native Collection by REF? I must have misunderstood the principle of it so thanks for this, as I really thought that the method would NOT copy it as it would do for a managed array...
     
  33. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,970
    It doesn't copy it. Let me give you an example:
    Code (CSharp):
    1. public void TakeThis(NativeArray<int> values)
    2. {
    3.     _values = values;
    4. }
    If you change the contents of _values the caller would see changes in his values array as well! That is the neat thing about native collections, even though they are structs.

    However, what really happens is that the assignment _values = values copies the NativeArray fields! The most important field internal unsafe void* m_Buffer; is a pointer to native memory, so even a copy will have the same value. Hence the copy of NativeArray will still use the same native memory location for read/write.

    If you were to add a ref to the method parameter (or "in" which is short for "readonly ref"), you probably prevent just one copy, or perhaps none - depends on how clever the C# compiler is. The assignment to _values implies a copy in any case (as far as I know). You may still be able to squeeze out some nanoseconds when using ref because NativeArray has several fields and I would expect them to be >=32 bytes.

    However, keep in mind, unless I'm (once again) stupid you cannot use ref, in or out modifiers when the method is supposed to be Burst compiled. Always throws an error. You'd have to use unsafe keyword and pointers to pass native collections between methods with Burst.

    From a syntactical point of view, I try to use in and ref as much as possible for any non-trivial struct in managed C# - both to express intent, and be safe that "in" parameters cannot be modified accidentally, and for a minimal increase in performance.
     
    Goularou and Antypodish like this.
  34. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    For the "in" parameter, there is still the possibility that the compiler creates a defensive copy.
     
    elliotc-unity likes this.
  35. Goularou

    Goularou

    Joined:
    Oct 19, 2018
    Posts:
    54
    Thanks a lot for this detailed answer. The key remains that Burst wont let us use IN or REF, and I am struggling with the BurstCompile for methods anyway (are they really Burst compiled?).
     
  36. Goularou

    Goularou

    Joined:
    Oct 19, 2018
    Posts:
    54
    Thanks. indeed, it makes sense: readyonly with a copy, thus safe.
     
  37. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    You don't have to decorate every method with BurstCompile, if you call a method in a bust-compiled method, it will also be compiled, there are then no problems with in and ref. You just have to mark as bust compiled the methods that are called from normal c# context.

    Stack variables can only be passed to another thread by copy, since each thread has its own exclusive stack and only he has access to it.
     
    Last edited: Sep 1, 2022
    Goularou likes this.