Search Unity

  1. Calling all beginners! Join the FPS Beginners Mods Challenge until December 13.
    Dismiss Notice
  2. It's Cyber Week at the Asset Store!
    Dismiss Notice

Anyone got Burst working with EntityCommandBuffer ?

Discussion in 'Data Oriented Technology Stack' started by 5argon, Aug 4, 2018.

  1. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,315
    I have previously trying to burst compile a job which contains ecb.SetSharedComponentData but burst debugger error says that function contains the use of `null` and not allowed to burst compile.

    Then today I use only `SetComponentData` and `AddComponentData`. It then says either you are setting a non existent component data (haven't add yet but try to set) or you cannot add 2 of the same component. (use add again instead of set) The Burst Debugger did not output compilation error this time.

    I don't know how it turns out that way. The function is going haywire and I guess it order the command in the buffer randomly, because it works correctly without [BurstCompile]. (I can turn on and off "Enable Burst Compilation" to reliably produce error and make the error disappear)

    So is this a known limitation that Burst will not work with any kind of Entity Command Buffer? I have tried both IJob/IParallelForJob and EntityCommandBuffer/EntityCommandBuffer.Concurrent , same result.
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,906
    It's a known limitation that burst does not work with EntityCommandBuffer at this stage.

    The standard approach I believe atm is to add your entities to a concurrent native queue then iterate that in a seperate job with the command buffer.

    -edit-

    source: https://forum.unity.com/threads/ent...nent-t-strange-behaviour.535424/#post-3529452

    -edit2-

    Also being sneaky does not seem to work either sadly

    Code (CSharp):
    1. // Does not work
    2. [BurstCompile]
    3. private struct TestJob : IJobParallelFor
    4. {
    5.     [ReadOnly] public EntityArray Entities;
    6.  
    7.     [BurstDiscard] public EntityCommandBuffer.Concurrent CommandBuffer { get; set; }
    8.  
    9.     public void Execute(int index)
    10.     {
    11.         SetComponent(Entities[index], new TestComponent());
    12.     }
    13.  
    14.     [BurstDiscard]
    15.     private void SetComponent<T>(Entity entity, T component) where T : struct, IComponentData
    16.     {
    17.         CommandBuffer.SetComponent(entity, component);
    18.     }
    19. }
     
    Last edited: Aug 5, 2018
    5argon likes this.
  3. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    620
    The only thing I've found that works on EntityCommandBuffer Burst-wise is DestroyEntity(). It seems like anything that needs ComponentType information (which would cover all the component related calls, and likely tie into create/create with archetype as well) won't work.
     
    5argon likes this.
  4. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,315
    Necro the thread but this might means Create and Add (and by extension Set?) are now working with Burst?
    https://docs.unity3d.com/Packages/com.unity.burst@0.2/changelog/CHANGELOG.html

    Screen Shot 2018-08-12 at 17.37.22.png


    Edit : Never mind it is still not possible. The point of compilation error now changes to the use of TypeManager which is based on statics and so still cannot be Burst compiled.

    Code (CSharp):
    1. /Users/Sargon/Library/Unity/cache/packages/staging-packages.unity.com/com.unity.entities@0.0.12-preview.8/Unity.Entities/Types/TypeManager.cs(86,13): error: Loading from a static field `System.Int32 Unity.Entities.TypeManager/StaticTypeLookup`1<T>::typeIndex` is not supported by burst
    2.  
    3. While processing function `System.Int32 Unity.Entities.TypeManager::GetTypeIndex<T>()`
    4.  
    5. While compiling job: System.Void Unity.Jobs.IJobParallelForExtensions/ParallelForJobStruct`1<NonLineStagingSystem/ShouldDrawJob>::Execute(T&,System.IntPtr,System.IntPtr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,System.Int32)
    6.  
    7. Compiler exception: System.NotSupportedException: Loading from a static field `System.Int32 Unity.Entities.TypeManager/StaticTypeLookup`1<T>::typeIndex` is not supported by burst
    8.   at Burst.Compiler.IL.ILVisitor.NotSupported (Burst.Compiler.IL.Syntax.ILInstruction inst) [0x0016b] in <a2ed9824c1aa48fdb9ed369837529c5f>:0
    9.   at Burst.Compiler.IL.ILVisitor.Ldsfld (Burst.Compiler.IL.Syntax.ILInstruction inst) [0x0006c] in <a2ed9824c1aa48fdb9ed369837529c5f>:0
    10.   at Burst.Compiler.IL.ILVisitor.CompileInternal (Burst.Compiler.IL.Syntax.ILInstruction inst) [0x00453] in <a2ed9824c1aa48fdb9ed369837529c5f>:0
    11.   at Burst.Compiler.IL.ILVerifier.CompileInternal (Burst.Compiler.IL.Syntax.ILInstruction inst) [0x00000] in <a2ed9824c1aa48fdb9ed369837529c5f>:0 /Users/Sargon/Library/Unity/cache/packages/staging-packages.unity.com/com.unity.entities@0.0.12-preview.8/Unity.Entities/Types/TypeManager.cs(85,9): error: The opcode instruction `IL_0013: ldtoken T` is not supported by burst
    12.  
    13. While processing function `System.Int32 Unity.Entities.TypeManager::GetTypeIndex<T>()`
    14.  
    15. ...
    16.  
     
    Last edited: Aug 12, 2018
    Opeth001 and wobes like this.
  5. NearAutomata

    NearAutomata

    Joined:
    May 23, 2018
    Posts:
    51
    I can confirm the same cryptic error message that @5argon posted and it took me a few hours to stumble upon this thread which confirmed that ECB isn't working with Burst yet.
     
  6. Tazadar66

    Tazadar66

    Joined:
    Aug 27, 2013
    Posts:
    54
    Do we have news about this ? :)
     
  7. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    4,764
    Its not supported yet.
     
  8. nttLIVE

    nttLIVE

    Joined:
    Sep 13, 2018
    Posts:
    77
    I'm trying to understand the pattern for using EntityCommandBuffers in job systems, especially with jobs that run in parallel.

    The first problem is that ECBs can't be used in burst compiled jobs. Is it confirmed that it will be supported?

    If so, will concurrent ECBs come in different interfaces? For example if I use a IJobChunk and I pass the chunk index there's a chance that the index 1 will be used twice.

    So far what I'm seeing is the best pattern is to stash every Entity/Component that needs to be created from a job and use an ECB on the main thread to create all the Entities so that BurstCompile isn't blocked. Is this correct?
     
  9. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,624
    must be a unique ID per job (such as the index passed to Execute() in an IJobParallelFor).

    Which mean you pass index of chunk, in your case for example, order form by job and by index per job
    No, in this case you loose performance gains (For Busted Jobs its right partially, for create you can use non bursted parallel job, but if you have small amount of entities\components it's not mater)
     
    Last edited: Oct 16, 2018
  10. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    Both .AddComponent and .SetComponent point to the:
    Code (CSharp):
    1. internal void AddEntityComponentCommand<T>(EntityCommandBufferChain* chain, int jobIndex, ECBCommand op, Entity e, T component) where T : struct
    2.         {
    3.             var typeIndex = TypeManager.GetTypeIndex<T>();
    4.             // NOTE: This has to be sizeof not TypeManager.SizeInChunk since we use UnsafeUtility.CopyStructureToPtr
    5.             //       even on zero size components.
    6.             var typeSize = UnsafeUtility.SizeOf<T>();
    7.             var sizeNeeded = Align(sizeof(EntityComponentCommand) + typeSize, 8);
    8.  
    9.             var cmd = (EntityComponentCommand*)Reserve(chain, jobIndex, sizeNeeded);
    10.  
    11.             cmd->Header.Header.CommandType = (int)op;
    12.             cmd->Header.Header.TotalSize = sizeNeeded;
    13.             cmd->Header.Header.SortIndex = chain->m_LastSortIndex;
    14.             cmd->Header.Entity = e;
    15.             cmd->ComponentTypeIndex = typeIndex;
    16.             cmd->ComponentSize = typeSize;
    17.  
    18.             byte* data = (byte*) (cmd + 1);
    19.             UnsafeUtility.CopyStructureToPtr(ref component, data);
    20.             if (RequiresEntityFixUp(data, typeIndex))
    21.             {
    22.                 if (op == ECBCommand.AddComponent)
    23.                     cmd->Header.Header.CommandType = (int) ECBCommand.AddComponentWithEntityFixUp;
    24.                 else if (op == ECBCommand.SetComponent)
    25.                     cmd->Header.Header.CommandType = (int) ECBCommand.SetComponentWithEntityFixUp;
    26.             }
    27.         }
    Which got me wondering, if we could pass the typeIndex manually, will it burst compile in theory? (Cache the type index, pass it as an int inside IComponentData to the job)

    Type index should be persistent, isn't it? If so, please consider making a public version of the Set/AddComponent methods for the EntityCommandBuffer.

    .Instantiate works now with burst. It would be really great to modify components in the same buffer queue.
     
    Last edited: Jan 25, 2019
  11. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    526
    a plain int would obviously not be safe (you can not check if index is correct for T without calling
    GetTypeIndex<T>()
    ).
    i can see some
    ComponentTypeInfo<T>
    struct with index and size that you initialize on the main thread (like ACCT) and you pass to ECB:
    SetComponent<T>(Entity e, T component, ComponentTypeInfo<T> info)
    . then validation would be
    if (info.typeIndex == 0) throw ...
     
  12. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    If the type indexes are assigned once and not changed ever, that would solve issue with checking / fetching types within bursted jobs. Less checks == less ops, less fetches.

    I don't see why they shouldn't be. Floating indexes for less memory consumption? Not worth it.

    So the persistent type indexes would definitely be a win.

    More than that, I'd rather prefer validation upon generation of the index itself, rather than each time entity is created / component data set.

    If the job is messing around with indexes - that's a jobs / programmers problem. I'd rather prefer being able to shoot myself in the foot, rather than being banned from using any kind of a weapon. (Including forks)

    Heck, we're already here in the unsafe code zone. wtf?
     
  13. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    526
    they are assigned once, but dynamically at runtime (i.e. the first time you call GetTypeIndex<T>), this will cause writing to static variables, and it is why it's not burst compatible.

    validation will only be done in debug mode, like all other validations. also remember that you can always pass a defaulted struct of any type, so there is no way in the runtime to prevent invalid data to exist (until it is used)
     
  14. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    If it is pre-allocated, it should not change. Type index is defined in generic static struct as static int.
    I don't think they change once they're created at least once.
    Unless I'm missing something from the source.

    I'd rather manually initialize data that should be manipulated at the start of the application. Something like:
    int typeIndex = TypeManager.GetTypeIndex<T>();

    Then use typeIndex in burst jobs all runtime.

    Sure it's "unsafe", but it'll allow to chain buffer calls inside burst jobs.

    Now that I've thinked about it, they're initialized at least once, because of the access to the GetTypeIndex<T>, so the case when index doesn't exist should apply when using cached value passed via IComponentData to the job.

    Well, unless nothing is passed, but that can be tested easily by asserting on index == 0 || index == -1;


    Also, I haven't found a place where that typeIndex value is written elsewhere than .GetTypeIndex.
     
    Last edited: Jan 25, 2019
  15. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    526
    they don't change but you can't ensure at compile time that the static int is not first accessed in a job. if it is, job will write to the static int. Burst does not support that (and I don't think it will)

    the "problem" right now is that ECB is calling GetTypeIndex<T> that is reading from a non-cons, non-readonly static variable. the compiler cannot deduce safety from only this information
     
  16. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    Well, I've just bypassed that by simply feeding the GetTypeIndex from the main thread and modifying the source.
    Problem arises when RequiresEntityFixUp is called, as it is also referencing a static type lookup with the TypeInfo in it.

    This part is what causing it to fail:
    Code (CSharp):
    1. if (RequiresEntityFixUp(data, typeIndex))
    2.             {
    3.                 if (op == ECBCommand.AddComponent)
    4.                     cmd->Header.Header.CommandType = (int) ECBCommand.AddComponentWithEntityFixUp;
    5.                 else if (op == ECBCommand.SetComponent)
    6.                     cmd->Header.Header.CommandType = (int) ECBCommand.SetComponentWithEntityFixUp;
    7.             }
    I wonder what's it for.
     
  17. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    Lol. Indeed it burst compiles and works without it. But I'm pretty sure removing it will break something.

    I was able to set the position data for the instantiated entity within burst job.

    Here's what I've came up with:
    EntityCommandBuffer:
    Code (CSharp):
    1. // MODIFIED
    2.         internal void AddEntityComponentCommandViaIndex<T>(EntityCommandBufferChain* chain,
    3.                                                            int jobIndex,
    4.                                                            ECBCommand op,
    5.                                                            Entity e,
    6.                                                            int typeIndex,
    7.                                                            T component) where T : struct
    8. {
    9.      // NOTE: This has to be sizeof not TypeManager.SizeInChunk since we use UnsafeUtility.CopyStructureToPtr
    10.      //       even on zero size components.
    11.      var typeSize = UnsafeUtility.SizeOf<T>();
    12.      var sizeNeeded = Align(sizeof(EntityComponentCommand) + typeSize, 8);
    13.  
    14.      var cmd = (EntityComponentCommand*)Reserve(chain, jobIndex, sizeNeeded);
    15.  
    16.      cmd->Header.Header.CommandType = (int)op;
    17.      cmd->Header.Header.TotalSize = sizeNeeded;
    18.      cmd->Header.Header.SortIndex = chain->m_LastSortIndex;
    19.      cmd->Header.Entity = e;
    20.      cmd->ComponentTypeIndex = typeIndex;
    21.      cmd->ComponentSize = typeSize;
    22.  
    23.      byte* data = (byte*) (cmd + 1);
    24.      UnsafeUtility.CopyStructureToPtr(ref component, data);
    25.      /*if (RequiresEntityFixUp(data, typeIndex))
    26.      {
    27.          if (op == ECBCommand.AddComponent)
    28.             cmd->Header.Header.CommandType = (int) ECBCommand.AddComponentWithEntityFixUp;
    29.            else if (op == ECBCommand.SetComponent)
    30.             cmd->Header.Header.CommandType = (int) ECBCommand.SetComponentWithEntityFixUp;
    31.      }*/
    32. }
    EntityCommandBuffer.Concurrent:
    Code (CSharp):
    1. public void AddComponentViaIndex<T>(int jobIndex, Entity e, int typeIndex, T component) where T : struct, IComponentData {
    2.      CheckWriteAccess();
    3.      var chain = ThreadChain;
    4.      m_Data->AddEntityComponentCommandViaIndex<T>(chain, jobIndex, ECBCommand.AddComponent, e, typeIndex, component);
    5. }
    Code (CSharp):
    1. public void SetComponentViaIndex<T>(int jobIndex, Entity e, int typeIndex, T component) where T : struct, IComponentData {
    2.      CheckWriteAccess();
    3.      var chain = ThreadChain;
    4.      m_Data->AddEntityComponentCommandViaIndex<T>(chain, jobIndex, ECBCommand.SetComponent, e, typeIndex, component);
    5. }
    Job receives the index in OnUpdate of the JobComponentSystem (TypeManager.GetTypeIndex<T>()), via struct prop, then it's possible to do the following:
    Code (CSharp):
    1. private void SetupEntity([ReadOnly] Entity prefab, [ReadOnly] ref SpawnRequestData data) {
    2.      Entity instance = Buffer.Instantiate(_jobIndex, prefab);
    3.      Buffer.SetComponentViaIndex(_jobIndex,
    4.          instance,
    5.          PositionTypeIndex,
    6.              new Position {
    7.                                Value = data.Position
    8.                           });
    9. }

    I guess, it's almost there.
     
    Last edited: Jan 25, 2019
  18. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    About RequiresEntityFixUp it seems it's used to re-align entities' offsets in memory.

    Summoning @Joachim_Ante. Maybe some light can be shed on why it's done this way. And also, ETA on burst Add/SetComponents would be awesome.
     
    Last edited: Jan 25, 2019
  19. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    526
    (and when you accidentally pass (...PositionTypeIndex, new Rotation()), you get a hard crash if you are lucky...)

    that approach should be sanitized by using the struct I mentioned:
    Code (CSharp):
    1. public struct ComponentTypeInfo<T> { internal int typeIndex; internal TypeInfo typeInfo;}
    2.  
    3. // add to TypeManager
    4. public static ComponentTypeInfo<T> GetComponentTypeInfo<T>() => new ComponentTypeInfo<T>{typeIndex = GetTypeIndex<T>(), typeInfo GetTypeInfo<T>()};
    then ECB/Concurrent will be
    AddComponent<T>(int jobIndex, Entity entity, ComponentTypeInfo<T> info, T component)
    , ensuring that the component and it's type info (index + all internal stuff) matches at compile time. you can still pass
    default
    , but then can be checked inside without accessing statics
     
    xVergilx likes this.
  20. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    Running code first, safety later. Although I like struct approach.

    Edit: Wait, does that mean we can use TypeInfo for the RequiresEntityFixUp?

    Does that mean it's possible to burst compile AddComponent/SetComponent without side effects?
     
  21. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    526
    I included TypeInfo because you mentioned the lookup in RequiresEntityFixUp, so you can pass that as well (or a pointer to it)

    before I thought only the size was needed
     
    xVergilx likes this.
  22. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,062
    And the answer is nope:
    TypeInfo is not blittable.
     
  23. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    526
    it contains a managed array (EntityOffsets) that it actually needs to calculate RequiresEntityFixUp. maybe you can bypass that via an unsafe pointer (
    unsafe struct Info<T> { [NativeDisableUnsafePointerRestriction] void* ptrToThatArray
    ), assuming that stuff is really readonly after initialization. then you need to do all calculations with unsafe pointer math to get the current behaviour
     
    xVergilx likes this.
  24. Elaixzar

    Elaixzar

    Joined:
    Dec 27, 2015
    Posts:
    2
    I know this is an old thread, but is this planned for the future? Not having a way to add/modify components and entities in a burst job is very limiting. It would be invaluable to have a command buffer able to be burst compiled
     
    Last edited: Sep 8, 2019
  25. francois85

    francois85

    Joined:
    Aug 11, 2015
    Posts:
    737
    I’m excited for has to drop , for now it just a //ToDo: burst and wait
     
  26. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,906
    2019.3 at some point supposedly, though it's out of alpha and we haven't heard anything in 2 months.
     
  27. tim_jones

    tim_jones

    Unity Technologies

    Joined:
    May 2, 2019
    Posts:
    37
    We are actively working on supporting `EntityCommandBuffer` in Burst-compiled code - we know it's important. We're aiming for the 2019.3 timeframe (and it will only be compatible with 2019.3+). Keep an eye on the release notes, we'll definitely mention it there when it's ready!
     
    Last edited: Sep 9, 2019
    fopsdev, JeffBert, mkracik and 14 others like this.
  28. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    155
    ECB is now supported by Burst. I'm curious to hear from anyone who's tried it out. Is it now worth using it when dealing with massive amounts of entities in multiple systems? (20k+) Because it wasn't previously. I'm talking only about the SetComponent method, not add or destroy as I know those trigger structural changes.
     
    Last edited: Nov 27, 2019
    charleshendry likes this.
  29. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,624
    Playback performance the same, cause Playback still not burstable. You just can use Set inside burst job which improve performance of this jobs itself (which wasn't bursted before)
     
  30. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    155
    Ah, that's too bad.