Search Unity

Accessing BlobArray in Component from System = Uninitialized Values

Discussion in 'Entity Component System' started by DennisWardAltair, Aug 19, 2019.

  1. DennisWardAltair

    DennisWardAltair

    Joined:
    Apr 4, 2019
    Posts:
    27
    I'm trying to simulate spheres moving along a high-resolution polyline (containing from 1000 to 5000 positions and a time value). There will be many spheres moving along each polyline (around 30 or so), and I want to update the position of the sphere along the path of the polyline. I've tried to structure my code such that each particle component will reference polyline data (without success!). I'm a noob at both Unity and ECS, and I'm having trouble setting this up: I created a BlobAssetReference and placed the polyline data in it, and I've referenced that data in each component as shown below for testing:

    Code (CSharp):
    1. public struct SphereComponent : IComponentData
    2. {
    3.     public int idx0;
    4.     public int idx1;
    5.     public float currentTime;
    6.     public BlobArray<Vector3> pl_pos;
    7.     public BlobArray<float> pl_time;
    8. }
    9.  
    10. public struct PolylineData
    11. {
    12.     public BlobArray<Vector3> pos;
    13.     public BlobArray<float> time;
    14.  
    15.     public static BlobAssetReference<PolylineData> ConstructPolyline()
    16.     {
    17.         BlobBuilder blobBuilder = new BlobBuilder(Allocator.Temp);
    18.         ref StreamlineData root = ref blobBuilder.ConstructRoot<PolylineData>();
    19.  
    20.         BlobBuilderArray<Vector3> positions = blobBuilder.Allocate<Vector3>(ref root.pos, 5);
    21.         BlobBuilderArray<float> times = blobBuilder.Allocate<float>(ref root.time, 5);
    22.  
    23.         positions[0] = new Vector3(0.1f, 1.2f, 0.2f);
    24.         positions[1] = new Vector3(1.1f, 2.2f, 0.3f);
    25.         positions[2] = new Vector3(0.5f, 3.2f, 0.2f);
    26.         positions[3] = new Vector3(0.3f, 4.2f, 0.1f);
    27.         positions[4] = new Vector3(0.7f, 5.2f, 0.0f);
    28.  
    29.         times[0] = 0.0f;
    30.         times[1] = 1.1f;
    31.         times[2] = 2.2f;
    32.         times[3] = 3.3f;
    33.         times[4] = 4.4f;
    34.  
    35.         BlobAssetReference<PolylineData> streamline_blob = blobBuilder.CreateBlobAssetReference<PolylineData>(Allocator.Persistent);
    36.  
    37.         blobBuilder.Dispose();
    38.  
    39.         return streamline_blob;
    40.     }
    I create a single sphere entity and set the component data as below (at which point the values are as expected):

    Code (CSharp):
    1.     void CreateParticleEntities(vModel model)
    2.     {
    3.         EntityManager emgr = World.Active.EntityManager;
    4.         EntityArchetype parch = emgr.CreateArchetype(typeof(SphereComponent), typeof(Translation), typeof(RenderMesh), typeof(LocalToWorld));
    5.         NativeArray<Entity> entarray = new NativeArray<Entity>(1, Allocator.Temp);
    6.         emgr.CreateEntity(parch, entarray);
    7.  
    8.         BlobAssetReference<PolylineData> pl_data = PolylineData.ConstructPolyline();
    9.  
    10.         for (int i = 0; i < entarray.Length; i++)
    11.         {
    12.             Entity pent = entarray[i];
    13.             emgr.SetComponentData(pent, new SphereComponent() { idx0 = 0, idx1 = 1, currentTime = 0.0f, pl_pos = pl_data.Value.pos, pl_time = pl_data.Value.time });
    14.             emgr.SetSharedComponentData(pent, new RenderMesh { mesh = particle_mesh, material = particle_material });
    15.         }
    16.     }
    The problem is that when I read the values from the system below, the position and time data values are unitialized, and not what I set in the ConstructPolyline function:

    Code (CSharp):
    1. public class SphereMovementSystem : ComponentSystem
    2. {
    3.     protected override void OnUpdate()
    4.     {
    5.         Entities.ForEach((ref SphereComponent particle, ref Translation translation ) => {
    6.             particle.idx0 += 1;
    7.             particle.idx1 += 1;
    8.             particle.currentTime += Time.deltaTime;
    9.             translation.Value.y += Time.deltaTime;
    10.  
    11.             // particle.sl_time and particle.sl_pos are both invalid (I'm not using particle.sl_pos yet...)
    12.             if (particle.currentTime > particle.sl_time[particle.sl_time.Length - 1])
    13.             {
    14.                 particle.currentTime = particle.sl_time[0];
    15.                 translation.Value.y = 0.0f;
    16.             }
    17.         });
    18.     }
    19. }
    I'm hoping to get some help on this scenario. I am having trouble understanding how to set up shared data that can be used by more that one entity. I had followed the forum question posted here for guidance, but I must have done something wrong
     
  2. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    This through me for a loop as well, but you shouldn't think of any BlobAssetArray or BlobPtr as being just "any" heap memory pointers, but actually memory offsets into the blob itself. Remove them from that structure header, and they no longer offset to the correct memory location.

    The best way to think of BlobAssets is an in-memory archive file, with your root structure as the header, and the other Blob* pointers and arrays as different types of offsets into that file. Your header can have local data, but it also has offsets to other parts of the file archive.

    You shouldn't separate the pl_data from the PolylineData asset reference, because those arrays have no meaning anywhere but local to that header struct.

    Instead assign the BlobAssetReference<PolylineData> to a field in your SphereComponent data structure, and access the elements from that instead of grabbing the subfields and assigning them elsewhere.

    These rules also apply to passing BlobArray<T> and friends to functions (as I found out), currently it's better to pass the pointer from
    GetUnsafePtr()
    and pass that and the length of the array to a function. Hopefully they add a AsNativeArray<T> and other helper methods (with more docs/tutorials) but this is the best you can do at the moment.
     
    eterlan likes this.
  3. DennisWardAltair

    DennisWardAltair

    Joined:
    Apr 4, 2019
    Posts:
    27
    Recursive - I tried your suggestion by adding the BlobAssetReference to the SphereComponent as you suggested and assigned the value when the entity is created:

    Code (CSharp):
    1. public struct SphereComponent : IComponentData
    2. {
    3.     public int idx0;
    4.     public int idx1;
    5.     public float currentTime;
    6.     public BlobAssetReference<StreamlineData> streamline;
    7. }
    But when I try to access it in the SphereMovementSystem::OnUpdate(), I'm getting errors stating that "Pointers and fixed sized buffers may only be used in an unsafe context" when I get an unsafe pointer from the BlobAssetReference on the SphereComponent:

    Code (CSharp):
    1. public class SphereMovementSystem : ComponentSystem
    2. {
    3.     protected override void OnUpdate()
    4.     {
    5.         Entities.ForEach((refSphereComponent particle, ref Translation translation) => {
    6.             particle.idx0 += 1;
    7.             particle.idx1 += 1;
    8.             particle.currentTime += Time.deltaTime;
    9.             translation.Value.y += Time.deltaTime;
    10.  
    11.             // Error below about unsafe context
    12.             Vector3* pos_data = (Vector3*)particle.streamline.Value.pos.GetUnsafePtr();
    13.  
    14.             ...
    15.         });
    16.     }
    17. }
    18.  

    I really appreciate your help on this! I'll keep digging but I thought you might have a suggestion to get around this one.

    Thanks, Dennis
     
  4. Deleted User

    Deleted User

    Guest

    Change your function to "protected unsafe override void OnUpdate". Also check "allow 'unsafe' code" in project settings.

    Cheers.
     
  5. DennisWardAltair

    DennisWardAltair

    Joined:
    Apr 4, 2019
    Posts:
    27
    Ok - I found that I needed to enable the "Allow 'unsafe' Code" in Project Settings->Player and bracket the code in OnUpdate with unsafe:

    Code (CSharp):
    1. public class SphereMovementSystem : ComponentSystem
    2. {
    3.     protected override void OnUpdate()
    4.     {
    5.         Entities.ForEach((ref SphereComponent particle, ref Translation translation) => {
    6.             particle.idx0 += 1;
    7.             particle.idx1 += 1;
    8.             particle.currentTime += Time.deltaTime;
    9.             translation.Value.y += Time.deltaTime;
    10.  
    11.             unsafe
    12.             {
    13.                 float* time_data = (float*)particle.streamline.Value.time.GetUnsafePtr();
    14.                 Vector3* pos_data = (Vector3*)particle.streamline.Value.pos.GetUnsafePtr();
    15.                  ...
    16.                  // Now I can correctly access time_data[] and pos_data[] within the unsafe block
    17.             }
    18.         });
    19.     }
    20. }
    I'm wondering if there isn't a better way to do this?

    Thanks, Dennis
     
  6. DennisWardAltair

    DennisWardAltair

    Joined:
    Apr 4, 2019
    Posts:
    27
    I'll give that a try! Thanks!
     
  7. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Deleted User likes this.
  8. Deleted User

    Deleted User

    Guest

    You have decorated it better :)