Search Unity

What is correct way to use EntityCommandBuffer.Concurrent inside IJobForEachWithEntity?

Discussion in 'Entity Component System' started by SergeyRomanko, May 23, 2019.

  1. SergeyRomanko

    SergeyRomanko

    Joined:
    Oct 18, 2014
    Posts:
    47
    Here is how I do this at the time being
    Code (CSharp):
    1. struct StorageErrandJob : IJobForEachWithEntity<FetchReceiver> {
    2.    
    3.     public EntityCommandBuffer.Concurrent commandBuffer;
    4.    
    5.     public void Execute(Entity entity, int index, [ReadOnly] ref FetchReceiver fetchReceiver) {
    6.    
    7.         ....
    8.        
    9.         commandBuffer.RemoveComponent<StorageInternalTag>(index, entity);
    10.     }
    11. }
    It works, but my concern is that I am using index incorrectly. Index in this code is index of the entity not job index. I am passing it RemoveComponent() as it is job index. It could be only 128 jobs, number of entites could be much more then 128. So it seems like this code would work only with small number of entities.

    Am I missing something? What is correct way to use EntityCommandBuffer.Concurrent inside IJobForEachWithEntity?
     
  2. CodeMonkeyYT

    CodeMonkeyYT

    Joined:
    Dec 22, 2014
    Posts:
    125
    ScriptsEngineer likes this.
  3. SergeyRomanko

    SergeyRomanko

    Joined:
    Oct 18, 2014
    Posts:
    47
    Here is code from EntityCommandBuffer.cs

    Code (CSharp):
    1.         internal void InitConcurrentAccess()
    2.         {
    3.             if (m_ThreadedChains != null)
    4.                 return;
    5.  
    6.             // PERF: It's be great if we had a way to actually get the number of worst-case threads so we didn't have to allocate 128.
    7.             int allocSize = sizeof(EntityCommandBufferChain) * JobsUtility.MaxJobThreadCount;
    8.  
    9.             m_ThreadedChains = (EntityCommandBufferChain*) UnsafeUtility.Malloc(allocSize, JobsUtility.CacheLineSize, m_Allocator);
    10.             UnsafeUtility.MemClear(m_ThreadedChains, allocSize);
    11.         }
    As I understand m_ThreadedChains are used inside EntityCommandBuffer.Concurrent to make it threadsafe by associating one chain with one job. Is this correct?
     
    Last edited: May 23, 2019
  4. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    I believe it's not the index of the entity, it's the index of the chunk the entity is in. IJFE runs a job for each chunk, not for each entity.
     
  5. SergeyRomanko

    SergeyRomanko

    Joined:
    Oct 18, 2014
    Posts:
    47
    I belive that IJFE runs a job for each chunk too. But I am also pretty sure that index is entity index.

    Take a look at this code from IJobForEach.gen.cs
    Code (CSharp):
    1. var chunk = chunks[blockIndex];
    2. int beginIndex = entityIndices[blockIndex];
    3. var count = chunk.Count;
    4.  
    5. ....
    6.  
    7. for (var i = 0; i != count; i++)
    8. {
    9.     jobData.Data.Execute(ptrE[i], i + beginIndex, ref UnsafeUtilityEx.ArrayElementAsRef<U0>(ptr0, i), ref UnsafeUtilityEx.ArrayElementAsRef<U1>(ptr1, i));
    10. }
    It shows that index is some base beginIndex plus i. i takes values from 0 to number entities in chunk.
     
    Sarkahn likes this.
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I am not an expert in multithreaded code but things are getting mixed up here. This 128 limit comes from optimizing concurrent access (cachelines, threads) it is at the moment over allocating because they don't determine the maximum theoretical parallel threads (which should be dependent on your cores).

    Something not to worry about...
     
  7. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Sorry, I did not read the OP carefully. I think what you want is the Thread Index in the job.

    [NativeSetThreadIndex] int threadID;

    and besides this you likely are better off by using the CG batch API. I.e. on the main thread remove all in one go
     
    Last edited: May 23, 2019
  8. SergeyRomanko

    SergeyRomanko

    Joined:
    Oct 18, 2014
    Posts:
    47
    I think [NativeSetThreadIndex] is for native containers I am not 100% sure that it works inside a job. I will do a test in a moment. Update: [NativeSetThreadIndex] works great inside jobs!

    Using CG batch API is really great advice. I will try it.

    I digged source code little bit more and here is what I found.

    jobIndex is not threadIndex! Isn't this a surprise? EntityCommandBuffer.Concurrent gets threadIndex as other containers do, it just declares [NativeSetThreadIndex] internal int m_ThreadIndex; . What is jobIndex if it is not the threadIndex? It looks like EntityCommandBuffer commands are executed not in the order they were recorded. Sorting operation is applyed to the commands before execution, this is where jobIndex is used. This sorting operation isn't trivial - it will keep order of commands with the same jobIndex, but groups of commands with the same jobIndex are sorted in order of ascending jobIndex.
     
    Last edited: May 23, 2019
  9. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    It's entity index.

    This is exactly for jobs. It's just sets int field in job for every thread.
    Yeap it's also used in native containers, but it's not limit.
    https://docs.unity3d.com/ScriptRefe...vel.Unsafe.NativeSetThreadIndexAttribute.html
     
    Last edited: May 23, 2019
    ScriptsEngineer likes this.