Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Make class a struct and use nativearrays in it

Discussion in 'Entity Component System' started by sietse85, Oct 7, 2020.

  1. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    How can i turn something like this:
    Code (CSharp):
    1. public class VoxelData
    2.     {
    3.         public Entity Entity;
    4.         public NativeArray<float> DensityMap;
    5.         public NativeArray<byte> BiomeMap;
    6.         public Vector2Int Chunk;
    7.         public int Lod;
    8.     }
    Into a struct that i can use with burst? DensityMap for example is a flattened 3d array that i will use for calculating collisions with voxels.

    I am using this in a Dictionary which im trying to swap into a NativeHashMap
    But then: ""ArgumentException: Client.Generic.VoxelData used in native collection is not blittable, not primitive, or contains a type tagged as NativeContainer""

    Is for example storing a pointer in that struct that points to the NativeArray an idea?

    Would like to know how you would approach this.
     
    Last edited: Oct 7, 2020
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    Code (CSharp):
    1. public struct VoxelData
    2.     {
    3.         public Entity Entity;
    4.         [NoAlias] public NativeArray<float> DensityMap;
    5.         [NoAlias] public NativeArray<byte> BiomeMap;
    6.         public Vector2Int Chunk;
    7.         public int Lod;
    8.     }
    Structs containing native containers are allowed in Burst. Native containers containing native containers are not allowed with or without Burst. If you put a container inside a struct, you should specify [NoAlias] in order to help Burst out with optimizing.
     
  3. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    @DreamingImLatios I'm sorry i haven't been clear enough in my question.

    The VoxelData class was used in a Dictionary <Vector2Int, VoxelData>
    When i am making it a struct and trying to turn the Dictionary to a NativeHashMap I'm getting the following exception:
    "ArgumentException: Client.Generic.VoxelData used in native collection is not blittable, not primitive, or contains a type tagged as NativeContainer"
    This is the reason i am asking.

    So i want to swap out Dictionary <Vector2Int, VoxelData> for a NativeHashMap <Vector2Int, VoxelData> So i can use it in jobs/burst.
     
    Last edited: Oct 7, 2020
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    In your case, you have a NativeHashMap (a NativeContainer) which contains VoxelData, which contains NativeArrays (a NativeContainer. So you have a NativeContainer inside a NativeContainer.

    There are plenty of posts on these forums that discuss different ways to store voxel data. For a start, you may want to try converting your NativeArrays into fixed arrays. Then you may want to store the voxel data in a NativeArray that uses 2D indexing math rather than a NativeHashMap.
     
  5. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    >There are plenty of posts on these forums that discuss different ways to store voxel data.
    I tried to search but didnt come up with anything good, could you perhaps share some links?

    > For a start, you may want to try converting your NativeArrays into fixed arrays
    Not sure what you mean by that. Currently my arrays are already fixed size. The are the same for each VoxelData class, maybe the name is a bit unhandy. VoxelData is actually a chunk of 64x64x255, the Density array gets sized this way. The Biome array only is 64x64 and stores which biome is at that location.

    >Then you may want to store the voxel data in a NativeArray that uses 2D indexing math rather than a NativeHashMap.
    I have all the indexing working in 3d. Chunks are 64x64x255 in size and the indexing works fine. I just need a way to use this with burst/jobs now i can't.

    In other words, i have everything working, but not with Jobs / Burst
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    Did you try searching for "voxel" in the "Data Oriented Technology Stack" subforum?

    I mean using the "fixed" keyword instead of NativeArray.

    NativeContainers don't work the same way as typical C# collections. They have built-in safety mechanisms so that you can use them in multi-threaded contexts without accidental race conditions. However, that imposes some additional restrictions which may prevent them from being a 1-1 replacement for the C# collections. You may have to redesign your data structures a little, or take risks diving into unsafe territory.
     
  7. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    So you might find there are other containers provided that could do what you want. Instead of collecting all information together into one struct and then trying to jam that into a dictionary. You could use a NativeHashMap and NativeMultiHashMap for what you're trying to achieve. Basically the following:

    Code (CSharp):
    1. public struct Data
    2. {
    3.     public NativeHashMap<Vector2Int, Entity> Entities;
    4.     public NativeMultiHashMap<Vector2Int, float> DensityMaps;
    5.     public NativeMultiHashMap<Vector2Int, byte> BiomeMaps;
    6.     public NativeHashMap<Vector2Int, Vector2Int> Chunks;
    7.     public NativeHashMap<Vector2Int, int> Lods;
    8. }
    This now allows you to pass that struct to a job and should help with being able to then have Burst compatible code. Do note you'll still have to deal with using the correct concurrent structures if you want to concurrently write to these items.
     
  8. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    Thanks i'll take a look at that. Never worked with NativeMultiHashMap before and i wonder how i would use the 3d flattened coordinate access in that.
     
  9. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    Solved it as follows
    Code (CSharp):
    1.  
    2. ChunkManager:
    3. NativeArray<float> DensityMap = new NativeArray<float>( DensityMapArraySize, Allocator.Persistent);
    4. NativeArray<byte> BiomeMap = new NativeArray<byte>(BiomeMapArraySize, Allocator.Persistent);
    5. VoxelData v = new VoxelData
    6. {
    7.     Chunk = point,
    8.     Lod = GetLOD(distance),
    9.     Entity = Entity.Null,
    10.     PtrBiomeMap = BiomeMap.GetUnsafePtr(),
    11.     PtrDensitryMap = DensityMap.GetUnsafePtr()
    12. };
    13.  
    14.  
    15. public struct VoxelData
    16.     {
    17.         public Entity Entity;
    18.         public unsafe void* PtrDensitryMap;
    19.         public unsafe void* PtrBiomeMap;
    20.         public Vector2Int Chunk;
    21.         public int Lod;
    22.     }
    And using NativeArrayUnsafeUtility to convert pointers to NativeArray

    I use this successfully in Jobs with Burst :)
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    You aren't getting errors about NativeArrays not being disposed? That code looks very prone to memory leaks.
     
  11. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    Code (CSharp):
    1. NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(
    2.                                     ChunkManager.Chunks[point].PtrBiomeMap,
    3.                                     ChunkManagerSystem.DensityMapArraySize,
    4.                                     Allocator.Invalid).Dispose();
    I just tested this. I do indeed get errors. I thought the above code would Dispose() of the array the correct way but I get exception here. What would be the correct way to free the memory here? Would I just use UnsafeUtilities here?
     
  12. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    how about:

    Code (CSharp):
    1. UnsafeUtility.MemClear(ChunkManager.Chunks[point].PtrBiomeMap, ChunkManagerSystem.BiomeMapArraySize * sizeof(byte));
    2.  
    3. UnsafeUtility.MemClear(ChunkManager.Chunks[point].PtrDensitryMap, ChunkManagerSystem.DensityMapArraySize * sizeof(float));
     
  13. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    MemClear should have nothing to do with this. The issue is that when you convert the NativeArray to the pointer, you lose track of the AtomicSafetyHandle and DisposeSentinel. Those are important to ensure what you are doing is safe. Otherwise you could have race conditions and memory leaks all over your code and no one will notice until it is too late.
     
  14. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    I would love some code example to implement my solution in the proper (memory safe) way.
     
  15. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    I don't know enough about your data's lifecycle to give you an example. Unlike in managed, there is no GC to clean up after you. So knowing the lifecycle of data is important.
     
  16. sietse85

    sietse85

    Joined:
    Feb 22, 2019
    Posts:
    99
    The lifecycle end when the chunk (that holds the DenstiyMap / BiomeMap ) gets to far away from the player. In that moment the arrays should be deallocated.

    In don't mind even to manage these 2 arrays completely manually with AllocHGlobal and use pointer aritmethic to do 3d coordinate access, if that is possible...
     
  17. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    If is very possible to do everything in an unsafe context. Have a look at UnsafeUtitltiy. The problem is that you are completely bypassing the safety system. If you want safety, you are probably going to have to write a custom Native Container that operates similarly to NativeHashMap (you can use UnsafeHashMap under the hood) and handles a pool of voxel chunks.