Search Unity

Native array of native arrays?

Discussion in 'Scripting' started by imaginaryhuman, Jul 21, 2018.

  1. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    I'm trying to use native arrays. I have a situation where I have a fairly large number of them, lets say there's 50, but it's totally variable. I need to pass all of these arrays into the Job in order for the Job System to be able to access any and all of them. Since the number of them is variable, it would seem to make sense to pass them in inside an array. Of course you can't use a regular array. So the ideal is a native array containing native arrays. This doesn't seem to be possible because its not a blittable type.

    As a second resort I tried using a native array of Int Pointers int* (with unsafe code) to contain pointers to each array's memory, but this to is not allowed because apparently int* is not a blittable type.

    A third option might be to store the int pointers (to each array) in a native array of ints, and then in the Job use some pointer functionality to convert the ints back to pointers? This is of course a bit more overhead.

    I also tried making a struct containing a native array and then making an array of these structs. This also was not allowed.

    The last resort I can see is to manually allocate some memory, stuff it with int pointers, and then pass a pointer to the memory to the Job. This seems like a pretty extreme last resort. However currently this is the only method that I could get working.

    Is there some other way to do this that I'm not seeing? To pass a variable number of native arrays to a job?
     
    Last edited: Jul 21, 2018
    RaveBox likes this.
  2. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Are these arrays homogeneous or do they have differing types?

    If they are all the same type, then maybe you could have 2 arrays. The first is the simple concatenation of all the data from all the arrays. The second would then be a set of values giving the length of each consecutive array in the first array.
     
  3. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    no i cannot concatenate the arrays.

    they are all of the same type however, arrays of ints.

    they also happen to be all the same length.

    it's just the number of them varies, and getting it to transfer say 5 arrays or 50 arrays or 5000 arrays, just seems difficult or next to impossible.
     
  4. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    I do see it being possible to do e.g.

    NativeArray<int>[] myarr;

    which is an array of native arrays. That seems ok. But what I ideally need is the int pointers to the memory buffers of those arrays and I don't want to have to compute them every time the job runs.

    That said trying to handle int pointers which are different lengths on 32bit and64bit seems like just as much a nightmare, so I might just stick to nativearray<int>[]
     
    Last edited: Jul 21, 2018
  5. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    In essence, you are wanting to send a reference to the data into the job. However, the safety system expressly states:
    So, if I am understanding correctly, it would seem you are trying to fundamentally circumvent the underlying mechanics of the system?
     
  6. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    yes i don't care about that.

    i am switching off the safety checks relating to concurrent access to the same memory buffers because I'm managing the safety on my own in this case.

    I'm also having an odd problem disposing of the native array. I can dispose of a single native array. But if I try to dispose of a "native array of native arrays" the Dispose() function is not even available.

    e.g.

    NativeArray<int>[] temp = new NativeArray<int>[5];
    temp.Dispose(); //Does not work

    The function is literally not available. Yet there has to be some storage of pointers or something somewhere that needs to get freed up.
     
  7. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    I think that is just a managed array that is being created. It happens to be an array of NativeArrays but none have been created yet.

    It is those NativeArrays (created using malloc, not the C# "new" operator) that will need disposing of. They will be created by including an allocator type, e.g.:
    new NativeArray<int>(1, Allocator.TempJob);
     
  8. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Yes but in order to have an "array of" those, surely there has to be memory somewhere with pointers or something pointing to those native arrays? If this was done in managed arrays there would be. And these native arrays are not being stored inline like structs.

    even if I don't assign any native arrays TO the array, it still seems like I should have to dispose of it at some point?

    or is it only the MEMORY of the array storage that gets freed, not the array "object"?
     
  9. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    That is my understanding of it. The C# new operator is requesting managed memory. By definition, it is not your responsibility to free that as it will be garbage collected. It is only memory that is directly allocated using malloc (which is unmanaged) that is your responsibility to release as it is outside the scope of the GC to be able to do so.
     
  10. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    hmm. new problem. A native array of arrays is apparently a reference type and cannot be passed to a job at all!
    " is not a value type. Job structs may not contain any reference types."
    lol

    So I guess passing multiple arrays is totally off the table in the job system.
    I presume this applies to native list as well? can't pass in a native list containing native arrays?
     
  11. villevli

    villevli

    Joined:
    Jan 19, 2016
    Posts:
    87
    When your arrays are the same type and same lenght you can easily combine them into a single array.
    (By saving the offsets and lenghts you could also combine variable lenght arrays into a single "buffer")

    Code (CSharp):
    1. T[][] arrays;
    2.  
    3. int numArrays
    4. int lenght
    5.  
    6. NativeArray<T> combinedArray = new NativeArray<T>(numArrays * lenght);
    7.  
    8. for (int i = 0; i < numArrays; ++i)
    9. {
    10.     for (int j = 0; j < length; ++j)
    11.     {
    12.         // i * lenght gives the offset of each array because they are the same lenght
    13.         combinedArray[i * lenght + j] = arrays[i][j];
    14.     }
    15. }
     
    Nyanpas likes this.
  12. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    I don't have the ECS downloaded so can't say for sure. The help only says that the NativeList is the same as the NativeArray, only difference being it can be resized. Therefore, I would assume the same applies to its range of eligible types.
     
  13. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Yah I don’t plan to copy arrays whatsoever especially not on a per frame basis.

    What I finally settled on for now, is I allocated some memory with the Unity ‘unsafe’ function ‘malloc’, the size of which is 8 bytes for every pointer I need to store. I did try an array of int* but its size varies based on 32bit/64bit platforms and this made it awkward to cast variables to it. So I made it a long* containing long pointers (pointers to longs technically) but basically 8 bytes per pointer (padded if its 32-bit). At each 8 bytes there is a new pointer. If I put an intptr in it, I first cast it to a long “value” (8-byte value of the address). It simply is a pointer with a bunch of zeros in the high end on 32-bit systems. Then I know that at each 8 bytes there is a pointer to use taking up potentially 8 bytes.

    I then use the Unity function GetUnsafeBufferPointerWithoutChecks() to get the memory pointer of the native array’s memory buffer. I cast the pointer values to “longs” and store them in the memory buffer I made, making use of the fact its a long* base address and just giving an offset. This way I can store multiple pointers in one buffer.

    Then when it comes time to do a multithreaded job, I pass just the long* of the memory buffer into the job (which does require an attribute and other stuff to make unsafe C# code available). Then the job can read off one long* at a given index in the memory buffer (based on the for loop index) and cast it to whatever type (typically int*) and start accessing the native array memory.

    Long story short it does work. But it’s not very elegant and while it is ‘safe’ right now it could be prone to errors, and I crashed Unity 5 times before I got it right ;-). I notice we can create ‘custom’ native containers though, which might hold some promise perhaps for a better way to do this. But for now I just want it to work.
     
    Last edited: Jul 22, 2018
  14. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Be careful with that- the size of a pointer is fixed per target architecture. So sizeof( int* ) == sizeof( long* ) == sizeof( byte* ) == sizeof( CMySuperMassiveObject* ). That is the fundamental nature of pointers (or reference types as C# calls them). It doesn't matter what is pointed to, because the pointer itself is always a known size in memory.

    According to the type table spec, using
    ulong
    (64 bit unsigned integral value) as the array type will be better for 64 bit builds, whilst
    uint
    (32 bit unsigned integral value) for 32 bit builds. Store the (appropriate) addresses in these unsigned integral values and then cast either of these to (int*) as needed.
     
  15. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I understand that the number of arrays is variable, but are you not able to determine a “worst case” at runtime? Then allocate accordingly and use native slices in the job. Likely parallelforbatch is also something that you might want - if your process the whole slice in parallel...

    I am not sure if isharedcomponentdata could help, did use it but I think it is reference based
     
  16. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Yes understood - the pointer itself is 32bit or 64bit regardless of what it points to. The difficulty I had was that I was using an array of int* and had a situation where I was being forced by unity to have to cast something to it in order to store something in it, and I couldn't do a "conditional cast" like cast it as an integer sometimes and cast it as a long sometimes. I didn't want to have to deal with "variable length" pointers. So I decided to just always make it an 8-byte address. When storing the pointer value I can just cast it to a "long". I figure memory probably won't exceed the massive size available with that many bits anytime soon.
     
  17. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Worst case scenario is 4 million entries. Can't do that.

    I am using parallelfor. I think the batch version can help me in some cases. But it doesn't solve anything to do with passing multiple native arrays to a job.
     
  18. shanecelis

    shanecelis

    Joined:
    Mar 26, 2014
    Posts:
    22
  19. IgorBoyko

    IgorBoyko

    Joined:
    Sep 28, 2020
    Posts:
    90
    Actually you can use pointers for this. Create a structure which stores a pointer to the "inner" array (UnsafeUtility may help with that) and its size. Then just put those structures inside upper-level NativeArray and access "inner" arrays by pointers. Not the most elegant solution, but definitely a working one.
     
    Neiist likes this.