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. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

SharedComponent & Burst Compile

Discussion in 'Burst' started by nttLIVE, Oct 3, 2018.

  1. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    80
    If you Inject a group that contains a SharedComponentDataArray and pass it to a Job that uses the BurstCompiler it will crash while trying to read the SharedComponentData with the error:

    error: Unable to get field because it is a class type


    It made me rethink why even use SharedComponentData? The Component I'm using only has Blittable data in it. The reason why I made it a SharedComponentData is because it doesn't change ever and I wanted to use the ComponentGroup filter feature. But Jobs can't take ComponentGroups so I manually filter in the Job with the injected group (which still gives me a huge boost of performance) and now I can't use Burst Compile because the data is a SharedComponent. I just refactored the component to ComponentData instead.

    So what's the point of SharedComponent? I understand the MeshRenderer use case but is there any good reason to use a SharedComponent if the data doesn't change often and the data can be shared between multiple entities? I think I don't understand the general idea of the SharedComponentData.
     
    Last edited: Oct 3, 2018
    noio likes this.
  2. dstilwagen

    dstilwagen

    Joined:
    Mar 14, 2018
    Posts:
    36
    You have the right idea for how a SharedComponent should be used. The problem your running into is because SharedComponentDataArray is not support by the burst compiler. Arrays are not currently supported by burst except NativeArray<T> and that is only available in 2018.3.0b3 or newer.
     
  3. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    80
    Is that so? My job seems to be able to take a ComponentDataArray fine. I thought the issue was that Burst Compiler can't take reference values and SharedComponentData turns your data into reference.
     
    pcysl5edgo likes this.
  4. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    The point of shared component data is that the data are shared as the name suggest. If you have thousand entities with the same shared component data with an int inside this int would not take space x thousand entities but just one. By adding shared component data to an entity is just "marking" that this entity is related to this data, not actually duplicating the data and add it to an entity like normal component.

    And by that idea IShared can store reference type more naturally, since attaching them to an entity would not require adding that reference type directly to the entity, breaking ECS predictable data layout concept. (If you are able to use that as a pointer then you could go anywhere)

    The reason I think because there is a C# List in the shared component data source code and Burst touch it. The C# List<object> is required to store reference type.

    What I do in my game is that I use ComponentGroup's filter in main thread first to add a tag component based on filtered result. This system should run early, then other system can inject on that tag as a way to inject filtered data.
     
    nttLIVE likes this.
  5. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    80
    The problem is that I would have two component groups, one which needs to be filtered based on the results of the first component group after some logic happens. I could filter the first group beforehand and schedule a job per filtered component array unless passing a NativeArray of NativeArray is a thing (?) (I don't know if that would be good versus the IJobParallelFor I'm already using) but the second component group can't simply be filtered without making a ton of tags and additional systems to manage the mess.

    Filtering manually seems to be a lot easier. Question is, is there a huge cost from filtering manually vs using ComponentGroup filters? I suppose there is somewhat of a cost filtering manually.

    I guess what I was doing wrong is that I need to pass in a NativeArray of the SharedComponent's data instead of passing in an injected group that contains a SharedComponentDataArray which would save me from the Burst Compiler from freaking out. (I think)
     
    Last edited: Oct 4, 2018
  6. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    I did a test before and manually filtering (on shared, not normal component) is quite slower https://gametorrahod.com/unity-ecs-why-and-how-to-use-the-foreachfilter-setfilter-5e464bb5bdb2#de22

    But I forgot if I tested it in the real device or not, because in-editor accessing data has a cost on safety check. Also I manually filter outside of a job without Burst.

    The idea around tagging is that injection's granularity is the "chunk". By adding a tag you are arranging your entities around in different chunks, allowing ECS to select everything in a chunk without exception, everything in there are what you want.

    By using IShared + filter, first you are getting too many chunks than required (since entity with the same IShared but different hash value will be moved to different chunk, unlike entity with same IComponentData but different value they are still in the same chunk), then the filter have to iterate through those chunks, choosing only the correct chunk by fast-equaling the hashed value of the IShared data that you want. It should cost less if you have more data that belongs in the same IShared because a filter should work on chunk level.

    By using manually filtering + IComponentData you are selecting one big chunk first then iterate through all data in that, which should cost more the higher amount of entities you have.

    *edit : Also ForEachFilter is no more, you should use chunk iteration instead https://github.com/Unity-Technologi...ster/Documentation/content/chunk_iteration.md

    It works directly on chunk, no different between IComponentData or IShared anymore since you are working on a chunk concept common to both.
    https://github.com/Unity-Technologi...ta-from-archetypechunkgetsharedcomponentindex
     
    Last edited: Oct 4, 2018
    nttLIVE likes this.
  7. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    You can use CreateArchetypeChunkArray, filter the resulting chunk array based on the SCD and pass the result to a IJobParallelFor. You can also filter in the job itself by getting the SCD's you want from GetAllUniqueSharedComponentData result and pass an NativeArray to the job which contains the valid SCD indices.
     
    nttLIVE likes this.
  8. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    Quick example:
    Code (CSharp):
    1. scdType = GetArchetypeChunkSharedComponentType<SomeSharedComponent>(true);
    2.  
    3. scdValues.Clear();
    4. scdValueMap.Clear();
    5. EntityManager.GetAllUniqueSharedComponentData(scdValue, scdValueMap);
    6. var chunks = EntityManager.CreateArchetypeChunkArray(query, Allocator.TempJob);
    7.  
    8. var chunkScdValue = new NativeArray<uint>(chunks.Length, Allocator.TempJob);
    9. for (var i = 0; i < chunks.Length; i++)
    10.     chunkScdValue[i] = scdValues[allVisibilityMap.IndexOf(chunks[i].GetSharedComponentIndex(scdType))];
    11.  
    12. // You may create multiple filtered chunk arrays here and pass them to the jobs or, if blittable, pass the chunkScdValue array to the job.
    13.  
    14. job = new SomeJob
    15. {
    16.     chunks = chunks,
    17.     chunkScdValue = chunkScdValue,
    18. }.ScheduleBatch(chunks.Length, 4, job);
     
  9. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    I haven't tried chunk iteration / ArchetypeChunk yet, but I think a summary of how you can filter IShared in the job goes like this :

    1. Create an EntityArchetypeQuery containing your IShared type and other data.

    2. EntityManager.CreateArchetypeChunkArray with the query you get all relevant chunks with IShared *type*, but still containinga wrong chunks with the IShared *value* that you don't want. You get NativeArray<ArchetypeChunk> (ACA).

    3. Get ArchetypeChunkType (ACT) outside a job, to work with ACA in the job later. Use GetArchetypeChunkSharedComponentType<T>().

    4. Each IShared value can be represented by an index rather than its value. In filtering work, we do not really want its value but just matching if it is the same or not. In the job, you cannot work on IShared's true value since it is related to reference type but just for comparison (filter) you can work on just the index.

    Determine an index representation of IShared value you want to filter before entering a job by EntityManager.GetAllUniqueSharedComponentData with 2 List overload (the value list and sharedComponentIndices list). Then iterate through the returned value List, finding the unique value that you want. Remember the int in sharedComponentIndices at the same index.

    5. Make a job with 3 public fields : ACA, ACTShared, the index representation of IShared value.

    6. In the job iterate through ACA. Remember that all chunks in this ACA has the IShared type you want, but not necessary the correct hashed value inside IShared, so we are looking for that (this is essentially "filtering") For each chunk use chunk.GetSharedComponentIndex(ACT) you will get an int. Compare the returned int value with the int you prepared before coming in the job. If it is the same you just found a chunk not only with the correct IShared type, but also the correct IShared value. You can then use additional ACT of your IComponentData to do some work here.
     
    Last edited: Oct 4, 2018
  10. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    80
    Finally got this to work, but man is it cryptic. My code has been rendered unreadable. Hopefully Unity has another way for this.

    This pattern also seems to be extremely easy to mess up with the number of variables required to make things work.
     
  11. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    80
    It also seems to run slower than the Injection method. I haven't done any Burst Compile test yet because I need to wait for ECB and Native Collection allocation in job for burst.

    Getting the SCD values does work in Burst Compile using this method though.
     
    Last edited: Oct 5, 2018
  12. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    I don't think it should be. Did you check where it is slow? One could first create a inverted scd map to get rid of IndexOf.
     
    nttLIVE likes this.
  13. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    80
    This could be it, I'll test that when I have time. But I doubt it would bring an impactful performance boost vs injection for my case since I can't Burst Compile yet. If I would be able to Burst Compile my job I could see the potential.
     
  14. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    80
    This is a must. I did some tests implementing a NativeHashMap and the performance is better than injection.
     
    julian-moschuering likes this.
  15. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    You could create one array for all SCD mapping global to local id and fill that for each type you need. Shouldn't take alot of memory but creation and lookup are even faster than NativeHashMap.
     
    nttLIVE likes this.