Search Unity

Questions for implementing ECS and Jobs into game

Discussion in 'Entity Component System' started by Rotary-Heart, Nov 1, 2018.

  1. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Alright, so I have been toying around with ECS and Jobs and I decided to start switching current systems that I had for a game I'm working on. So I want to see if I'm indeed understanding how to setup them properly.

    My first system is pretty much a look at camera like system. It will make the objects to have the same Y euler rotation as the camera. So here's how my system is setup right now:

    Code (CSharp):
    1. [BurstCompile]
    2. struct PositionUpdateJob : IJobParallelForTransform
    3. {
    4.     [ReadOnly]
    5.     public float cameraRotation;
    6.  
    7.     public void Execute(int i, TransformAccess transform)
    8.     {
    9.        Quaternion worldRotation = transform.rotation;
    10.         Vector3 eulerRotation = worldRotation.eulerAngles;
    11.  
    12.         //Ignore if the difference between the rotation is minimum
    13.         if (Compare(eulerRotation.y, cameraRotation))
    14.             return;
    15.  
    16.         eulerRotation.y = cameraRotation;
    17.  
    18.         worldRotation.eulerAngles = eulerRotation;
    19.         transform.rotation = worldRotation;
    20.     }
    21. }
    22.  
    23. public void Update()
    24. {
    25.     //Only execute if the camera is changing rotation
    26.     if (NewBehaviourScript.CamRotationDirection == 0)
    27.         return;
    28.  
    29.     m_Job = new PositionUpdateJob()
    30.     {
    31.         cameraRotation = Camera.main.transform.parent.eulerAngles.y,
    32.     };
    33.  
    34.     m_PositionJobHandle = m_Job.Schedule(m_TransformsAccessArray);
    35. }
    36.  
    37. public void LateUpdate()
    38. {
    39.     m_PositionJobHandle.Complete();
    40. }
    41.  
    42. private void OnDestroy()
    43. {
    44.     m_TransformsAccessArray.Dispose();
    45. }
    Here's my compare function in case that is needed
    Code (CSharp):
    1. bool Compare(float a, float b, float allowedDifference = 0.001f)
    2. {
    3.     float absA = math.abs(a);
    4.     float absB = math.abs(b);
    5.     float diff = math.abs(a - b);
    6.  
    7.     if (a == b)
    8.     {
    9.         return true;
    10.     }
    11.     else if (a == 0 || b == 0 || diff < float.MinValue)
    12.     {
    13.         // a or b is zero or both are extremely close to it
    14.         // relative error is less meaningful here
    15.         return diff < (allowedDifference * float.MinValue);
    16.     }
    17.     else
    18.     {
    19.         return diff / math.min((absA + absB), float.MaxValue) < allowedDifference;
    20.     }
    21. }

    m_TransformsAccessArray will contain the array of transforms that I need to rotate. Currently it seems to be working fine and it's 15 FPS faster than with the old MonoBehaviour system with 5000 objects on scene.

    Is this the correct setup I should be following for this kind of system? Right now the GO to rotate can't be pure ECS since I need to use some Unity systems that are not pure ready.
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Well you're not using ECS, you're just using jobs but that isn't an issue; there's a reason jobs is detached to ECS. Being able to optimize existing code with jobs to get performance is great.

    So what you're doing seems fine and the fact that you can make such a simple change and get noticeable performance benefits is fantastic.
     
    NotaNaN and Rotary-Heart like this.
  3. psuong

    psuong

    Joined:
    Jun 11, 2014
    Posts:
    126
    Well, you can certainly make the set up a Hybrid setup. It's a great segway into using a Pure ECS implementation. It looks like you're still using MonoBehaviours and scheduling jobs in Update / and completing them in LateUpdate. In terms of correctness, it's mostly right. Unless that MonoBehaviour is destroyed, you don't dispose the TransformAccessArray. Scheduling the Job in update and letting it complete in LateUpdate is fine in a MonoBehaviour.
    If you want to head in the direction of using a Hybrid approach, you would:

    * Get the data you need from a ComponentGroup / Dependency Injection
    * Create a Job/ComponentSystem
    * Override OnUpdate()
    * Schedule and allow jobs to complete in OnUpdate()
    * Dispose at the end of OnUpdate()

    I think taking a look at their TwoStickShooter Hybrid or another project which uses Hybrid provided by Unity can shed some light on how to convert over to a Hybrid approach.
    https://github.com/Unity-Technologi.../master/Samples/Assets/TwoStickShooter/Hybrid
     
    Rotary-Heart likes this.
  4. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Alright, following both of your suggestion to use ECS. Here's my new system:

    Code (CSharp):
    1. public class RotationSystem : JobComponentSystem
    2. {
    3.     /// <summary>
    4.     /// Struct used to filter the entities
    5.     /// </summary>
    6.     struct Filter
    7.     {
    8.         public readonly int Length;
    9.         public ComponentArray<PawnComponent> components;
    10.         public TransformAccessArray transformsAccessArray;
    11.     }
    12.  
    13.     [Inject]
    14.     Filter m_filter;
    15.     float cameraRotation;
    16.  
    17.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    18.     {
    19.         RotationUpdateJob myJob = new RotationUpdateJob()
    20.         {
    21.             cameraRotation = Camera.main.transform.parent.eulerAngles.y,
    22.         };
    23.  
    24.         return myJob.Schedule(m_filter.transformsAccessArray);
    25.     }
    26.  
    27.     [BurstCompile]
    28.     struct RotationUpdateJob : IJobParallelForTransform
    29.     {
    30.         [ReadOnly]
    31.         public float cameraRotation;
    32.  
    33.         public void Execute(int i, TransformAccess transform)
    34.         {
    35.             Quaternion worldRotation = transform.rotation;
    36.             Vector3 eulerRotation = worldRotation.eulerAngles;
    37.  
    38.             //Ignore if the difference between the rotation is minimum
    39.             if (Compare(eulerRotation.y, cameraRotation))
    40.                 return;
    41.  
    42.             eulerRotation.y = cameraRotation;
    43.  
    44.             worldRotation.eulerAngles = eulerRotation;
    45.             transform.rotation = worldRotation;
    46.         }
    47.     }
    48. }
    Which seems to perform even better, but I'm wondering how I can spread the load into all worker threads?. Right now the job is being executed by only one. This is a screenshot of 50,000 rotating hybrid GameObjects.

    Capture.PNG
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    TransformAccess will split based on root transform.

    If all your transforms are under the same root parent, it'll only work on a single thread.

    If you split them between 3 root parents, it'll be split across 3 workers.
     
    Rotary-Heart likes this.
  6. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Alright so for my next system I have been trying to see if it's worh it to change my Monobehaviours meshes to pure, but I cannot seem to get any better performance than GameObjects with MeshRenderer components. I am not using any custom system since first I wanted to be sure that it would be faster before attempting anything. So here are my tests:

    All tests have 5,000 static objects spawned with the same settings. Shadows on, receive shadows and they all are using the same material (standard material with a texture). The mesh is a simple quad.

    Traditional
    Capture.PNG

    Hybrid
    Capture.PNG

    Pure
    Capture.PNG

    Does anyone know of any trick to draw meshes that is faster than traditional way? Note that the only code I have is the instantiation of the 5,000 GameObjects and the creation of the 5,000 entities for the pure system.
     
  7. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Did you tick this on your material

    upload_2018-11-5_13-34-30.png
     
    Rotary-Heart likes this.
  8. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    That was stupid... I thought I did, but I miss clicked and enabled Double Sided Global Illumination instead of GPU Instancing....

    Perfect, now I'm getting better performance. Now is there any way to change a material property of a specific entities? I'm wondering if it's possible since it uses SharedComponentData.

    I want to see if I can change the material offset and tiling, so that I can modify my SpriteSheet animator with pure too.
     
  9. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
  10. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    While I couldn't find a way to use the property blocks above I managed to make the system, but I'm having an error that I cannot figure out how to fix. It seems like if you change any SharedComponentData the EntityArray gets deallocated so it throws the following error:
    Code (CSharp):
    1. InvalidOperationException: The NativeArray has been deallocated, it is not allowed to access it
    Here's my current system:
    Code (CSharp):
    1. ComponentGroup group;
    2.  
    3. protected override void OnCreateManager()
    4. {
    5.     base.OnCreateManager();
    6.  
    7.     group = EntityManager.CreateComponentGroup(typeof(MeshInstanceRenderer), typeof(SpriteAnimator));
    8. }
    9.  
    10. protected override void OnUpdate()
    11. {
    12.     EntityArray entitites = group.GetEntityArray();
    13.  
    14.     for (int i = 0; i < entities.Length; i++)
    15.     {
    16.         Entity entity = entities[i];
    17.  
    18.         MeshInstanceRenderer renderer = EntityManager.GetSharedComponentData<MeshInstanceRenderer>(entity);
    19.  
    20.         //Here I would change something, if it stops throwing the error
    21.  
    22.         EntityManager.SetSharedComponentData(entity, renderer);
    23.     }
    24. }
    As you can see I'm not even making any change, just setting the renderer back and it throws the error. The error points to the line
    Entity entity = entities[i];
    Am I missing a step here?

    If the SetSharedComponentData line is removed everything works again. Also tried with [Inject] and the same error appears.

    EDIT: Error bellow in case that it helps
    Code (CSharp):
    1. InvalidOperationException: The NativeArray has been deallocated, it is not allowed to access it
    2. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) <0x394ef160 + 0x00052> in <d5141ab31a774986ab28d43b33133e56>:0
    3. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at C:/buildslave/unity/build/Runtime/Export/AtomicSafetyHandle.bindings.cs:148)
    4. Unity.Entities.EntityArray.get_Item (System.Int32 index) (at Library/PackageCache/com.unity.entities@0.0.12-preview.19/Unity.Entities/Iterators/EntityArray.cs:40)
    5. SpriteAnimatorSystem.OnUpdate (Unity.Jobs.JobHandle inputDeps) (at Assets/SpriteAnimatorSystem.cs:67)
    6. Unity.Entities.JobComponentSystem.InternalUpdate () (at Library/PackageCache/com.unity.entities@0.0.12-preview.19/Unity.Entities/ComponentSystem.cs:507)
    7. Unity.Entities.ScriptBehaviourManager.Update () (at Library/PackageCache/com.unity.entities@0.0.12-preview.19/Unity.Entities/ScriptBehaviourManager.cs:77)
    8. Unity.Entities.ScriptBehaviourUpdateOrder+DummyDelagateWrapper.TriggerUpdate () (at Library/PackageCache/com.unity.entities@0.0.12-preview.19/Unity.Entities/ScriptBehaviourUpdateOrder.cs:703)
     
    Last edited: Nov 5, 2018
  11. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Entities groups in chunk by Archetype and by SharedComponentData (SCD), in addition SCD stores not per entity but per chunk, which mean - if you change SCD value it's invalidate chunk (because after change entities must move to other chunk), you must use barriers and\or EntityCommandBuffer (best way) or call UpdateInjectedComponentGroups() after every chage (bad way, slow way, dont do this often :D )
     
    Rotary-Heart likes this.
  12. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Awesome that did it! Many thanks! Here's how it works with 5,000 fully animated objects at different fps and random colors.
    Capture.PNG
    Now to try to move it to a JobComponentSystem. Is there anyway I could either get MeshInstanceRenderer from the Job or pass it from the System? When I tried to pass it, it throws an error about it being marked as WriteOnly and I'm trying to access it.

    Here's my JobSystem:
    Code (CSharp):
    1. protected override JobHandle OnUpdate(JobHandle inputDeps)
    2. {
    3.     buffer = m_barrier.CreateCommandBuffer();
    4.  
    5.     job = new AnimatorJob
    6.     {
    7.         deltaTime = Time.deltaTime,
    8.         renderers = group.GetSharedComponentDataArray<MeshInstanceRenderer>(),
    9.         animators = group.GetComponentDataArray<SpriteAnimator>(),
    10.         entities = group.GetEntityArray(),
    11.         commands = buffer.ToConcurrent()
    12.     };
    13.  
    14.     handle = job.Schedule(job.animators.Length, 64, inputDeps);
    15.     handle.Complete();
    16.  
    17.     return handle;
    18. }
    19.  
    20. struct AnimatorJob : IJobParallelFor
    21. {
    22.     [ReadOnly]
    23.     public float deltaTime;
    24.  
    25.     public SharedComponentDataArray<MeshInstanceRenderer> renderers;
    26.     [ReadOnly]
    27.     public EntityArray entities;
    28.     [ReadOnly]
    29.     public ComponentDataArray<SpriteAnimator> animators;
    30.     public EntityCommandBuffer.Concurrent commands;
    31.  
    32.     public void Execute(int index)
    33.     {
    34.         //This throws an error
    35.         MeshInstanceRenderer renderer = renderers[index];
    36.     }
    37. }
    It throws the following error that points to
    MeshInstanceRenderer renderer = renderers[index];

    Code (CSharp):
    1. InvalidOperationException: The native container has been declared as [WriteOnly] in the job, but you are reading from it.
    2. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) <0x3950fa30 + 0x00052> in <d5141ab31a774986ab28d43b33133e56>:0
    3. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at C:/buildslave/unity/build/Runtime/Export/AtomicSafetyHandle.bindings.cs:148)
    4. Unity.Entities.SharedComponentDataArray`1[T].get_Item (System.Int32 index) (at Library/PackageCache/com.unity.entities@0.0.12-preview.19/Unity.Entities/Iterators/SharedComponentDataArray.cs:40)
    5. SpriteAnimatorSystem+AnimatorJob.Execute (System.Int32 index) (at Assets/SpriteAnimatorSystem.cs:88)
    6. Unity.Jobs.IJobParallelForExtensions+ParallelForJobStruct`1[T].Execute (T& jobData, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at C:/buildslave/unity/build/Runtime/Jobs/Managed/IJobParallelFor.cs:43)
     
    Last edited: Nov 5, 2018
  13. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    First - remove ReadOnly from Concurrent ECB
     
  14. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Fixed, that was a bad copy/paste. I'm not using ReadOnly on it.
     
  15. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Second - mark field "renderers" in job [ReadOnly]
     
  16. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Still gives the same error
     
  17. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Are you shure? It's all code?
     
  18. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Yes, here's the full system class:
    Code (CSharp):
    1. public class SpriteAnimatorSystem : JobComponentSystem
    2. {
    3.     public class SpriteAnimatorBarrier : BarrierSystem { }
    4.  
    5.     [Inject]
    6.     private SpriteAnimatorBarrier m_barrier;
    7.  
    8.     float deltaTime;
    9.     EntityCommandBuffer buffer;
    10.     ComponentGroup group;
    11.     JobHandle handle;
    12.     AnimatorJob job;
    13.  
    14.     protected override void OnCreateManager()
    15.     {
    16.         base.OnCreateManager();
    17.  
    18.         group = EntityManager.CreateComponentGroup(typeof(MeshInstanceRenderer), typeof(SpriteAnimator));
    19.     }
    20.  
    21.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    22.     {
    23.         buffer = m_barrier.CreateCommandBuffer();
    24.  
    25.         job = new AnimatorJob
    26.         {
    27.             deltaTime = Time.deltaTime,
    28.             renderers = group.GetSharedComponentDataArray<MeshInstanceRenderer>(),
    29.             animators = group.GetComponentDataArray<SpriteAnimator>(),
    30.             entities = group.GetEntityArray(),
    31.             commands = buffer.ToConcurrent()
    32.         };
    33.  
    34.         handle = job.Schedule(job.animators.Length, 64, inputDeps);
    35.         handle.Complete();
    36.  
    37.         return handle;
    38.     }
    39.  
    40.     struct AnimatorJob : IJobParallelFor
    41.     {
    42.         [ReadOnly]
    43.         public float deltaTime;
    44.  
    45.         [ReadOnly]
    46.         public SharedComponentDataArray<MeshInstanceRenderer> renderers;
    47.         [ReadOnly]
    48.         public EntityArray entities;
    49.         [ReadOnly]
    50.         public ComponentDataArray<SpriteAnimator> animators;
    51.         public EntityCommandBuffer.Concurrent commands;
    52.  
    53.         public void Execute(int index)
    54.         {
    55.             //This throws an error
    56.             MeshInstanceRenderer renderer = renderers[index];
    57.         }
    58.     }
    59. }
    And the error: (Pointing to the same line
    MeshInstanceRenderer renderer = renderers[index];
    )
    Code (CSharp):
    1. InvalidOperationException: The native container has been declared as [WriteOnly] in the job, but you are reading from it.
    2. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) <0x19a0fa30 + 0x00052> in <d5141ab31a774986ab28d43b33133e56>:0
    3. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at C:/buildslave/unity/build/Runtime/Export/AtomicSafetyHandle.bindings.cs:148)
    4. Unity.Entities.SharedComponentDataArray`1[T].get_Item (System.Int32 index) (at Library/PackageCache/com.unity.entities@0.0.12-preview.19/Unity.Entities/Iterators/SharedComponentDataArray.cs:40)
    5. SpriteAnimatorSystem+AnimatorJob.Execute (System.Int32 index) (at Assets/SpriteAnimatorSystem.cs:74)
    6. Unity.Jobs.IJobParallelForExtensions+ParallelForJobStruct`1[T].Execute (T& jobData, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at C:/buildslave/unity/build/Runtime/Jobs/Managed/IJobParallelFor.cs:43)
     
  19. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    FYI. You must call GetComponentGroup. EntityManager.CreateComponentGroup is Unity rudiment and it's not for usage. More precisely, it is an internal method.
    upload_2018-11-5_17-40-53.png

    And all works fine when I try your code
    Code (CSharp):
    1.  
    2.  
    3.     struct AnimatorJob : IJobParallelFor
    4.     {
    5.         [ReadOnly]
    6.         public float deltaTime;
    7.  
    8.         [ReadOnly]
    9.         public SharedComponentDataArray<MeshInstanceRenderer> renderers;
    10.         [ReadOnly]
    11.         public EntityArray entities;
    12.         [ReadOnly]
    13.         public ComponentDataArray<SpriteAnimator> animators;
    14.         public EntityCommandBuffer.Concurrent commands;
    15.  
    16.         public void Execute(int index)
    17.         {
    18.             MeshInstanceRenderer renderer = renderers[index];
    19.             Debug.Log("All fine");
    20.         }
    21.     }
    22.  
    upload_2018-11-5_17-41-31.png

    I think problem in other place, this error not point to some code row it's just info. You must check other systems.
     
    Last edited: Nov 5, 2018
  20. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    I must be missing something else then, because I still got the error. How are you creating the system? What version are you running right now? My tests are with Unity 2018.3.0b8 and:
    Capture.PNG
     
  21. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Are you shure code compiled correctly? Try put debug log in first row after OnUpdate() for check that Unity compile this version
     
  22. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Hmm..... Honestly now I'm not even sure why it's throwing the error. It seems like it runs fine a couple of times, but suddenly it throws the error. Here the Update ran 2 times before the error happened: (Only using 2 entities for this test)
    Capture.PNG
     
  23. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Do you have another [Job]ComponentSystem that uses MeshInstanceRenderer?

    I've found there are many gotchas and dependency issues when using SharedComponentData. I'd probably only manipulate and use them in a ComponentSystems as unity does not seem to automatically handle their dependencies over multiple systems well.
     
    Last edited: Nov 5, 2018
  24. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    No, but I noticed that the error appears randomly so I have decided to keep the system as a ComponentSystem instead of a JobComponentSystem, at least now I can move a lot of things to pure.
     
  25. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Alright, so I'm back with this code. Trying to move over to chunk iteration, but I'm having some issues. Here's my current pseudo code for it:
    Code (CSharp):
    1. ArchetypeChunkSharedComponentType<MeshInstanceRenderer> m_meshInstanceRendererType;
    2. ArchetypeChunkComponentType<Animations.SpriteSheetAnimator> m_spriteSheetAnimatorType;
    3. ArchetypeChunkComponentType<WorldMeshRenderBounds> m_worldMeshRendererBoundsType;
    4.  
    5. ComponentGroup m_group;
    6.  
    7. protected override void OnCreateManager()
    8. {
    9.     base.OnCreateManager();
    10.  
    11.     m_group = GetComponentGroup(ComponentType.ReadOnly<MeshInstanceRenderer>(), ComponentType.ReadOnly<Animations.SpriteSheetAnimator>(), ComponentType.ReadOnly<WorldMeshRenderBounds>());
    12. }
    13.  
    14. [BurstCompile]
    15. protected override void OnUpdate()
    16. {
    17.     // Do I still need this???
    18.     m_buffer = m_barrier.CreateCommandBuffer();
    19.  
    20.     m_deltaTime = Time.deltaTime;
    21.  
    22.     // Get each ArchetypeChunkComponentType
    23.     m_meshInstanceRendererType = GetArchetypeChunkSharedComponentType<MeshInstanceRenderer>();
    24.     m_spriteSheetAnimatorType = GetArchetypeChunkComponentType<Animations.SpriteSheetAnimator>();
    25.     // I think this will work???
    26.     m_worldMeshRendererBoundsType = GetArchetypeChunkComponentType<WorldMeshRenderBounds>();
    27.  
    28.     // Get the chunks and process each
    29.     var chunks = m_group.CreateArchetypeChunkArray(Allocator.TempJob);
    30.     for (int i = 0; i < chunks.Length; ++i)
    31.     {
    32.         Process(chunks[i]);
    33.     }
    34.  
    35.     // Don't forget to dispose the chunks
    36.     chunks.Dispose();
    37. }
    38.  
    39. private void Process(ArchetypeChunk chunk)
    40. {
    41.     // Use the ArchetypeChunkComponentType to get an array of each component
    42.     NativeArray<Animations.SpriteSheetAnimator> animators = chunk.GetNativeArray(m_spriteSheetAnimatorType);
    43.     NativeArray<WorldMeshRenderBounds> worldMesRendererBounds = chunk.GetNativeArray(m_worldMeshRendererBoundsType);
    44.  
    45.     // Update the components here
    46.     for (int i = 0; i < chunk.Count; ++i)
    47.     {
    48.         Animations.SpriteSheetAnimator animator = animators[i];
    49.         WorldMeshRenderBounds bounds = worldMesRendererBounds[i];
    50.  
    51.         // Do the checks for frames here and modify the animator data
    52.  
    53.         // Is this how I setup the changes on the animator instead of using EntityManager.SetComponentData(entity, animator);???
    54.         animators[i] = animator;
    55.  
    56.         // Update MeshInstanceRenderer???
    57.     }
    58. }
    Do I still need the barrier to modify the entity components?
    How can I modify the MeshInstanceRenderer? Haven't seen any example that is using any shared component.
     
  26. ProtonOne

    ProtonOne

    Joined:
    Mar 8, 2008
    Posts:
    406
    @Rotary-Heart I was just getting the same intermittent error you were seeing:
    Code (CSharp):
    1. InvalidOperationException: The native container has been declared as [WriteOnly] in the job, but you are reading from it.
    In my case it was from the code that spawned the entities. I removed the MeshInstanceRenderer from the Archetype, then when creating the entity I used EntityCommandBuffer.AddSharedComponent instead of EntityCommandBuffer.SetSharedComponent.

    So I think that having SharedComponents in the Archetype is bad since changing them invalidates chunks ( :confused:???). Luckily I don't need to change any shared components after they are assigned the first time, maybe this is an ECS bug, I don't know. The learning curve is steep, but the performance gains make it worth it :)
     
  27. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    No, it's absolutley fine and more performant way to have SCD in archetype in cotrary with adding at next line (I tell about creating entity with some CD and SCD from scratch). All structural changes invalidate chunks, adding\removing entitites, adding\removing component data, adding\removing\changing shared component data, because SCD it's not per entity basis it's per chunk, etities groups in chunk by archetype and then by shared component data value it's by design.
     
  28. ProtonOne

    ProtonOne

    Joined:
    Mar 8, 2008
    Posts:
    406
    @eizenhorn I created as simple a test as I could think of that reliably produces this error.

    It just spawns 100 cubes per frame from a job, then another job destroys them after 2 seconds. The first 100 cubes work okay, then the error starts.

    There is also a commented out version that does work fine, but does not spawn using jobs.

    Scary thing is that my hack of taking the MeshInstanceRenderer out of the Archetype doesn't actually make the error go away...

    It's entirely possible I'm doing something wrong/crazy, is there anything you can see?

    Unity 2018.3.0b11
    Entities 0.012-preview.21
    Jobs -> Leak Detection (Native Containers) = ON

    CubeSpawner.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using Unity.Rendering;
    6. using Unity.Transforms;
    7. using Unity.Jobs;
    8. using Unity.Collections;
    9. using Unity.Mathematics;
    10.  
    11. public class CubeSpawner : MonoBehaviour
    12. {
    13.     public MeshInstanceRendererComponent CubeRendererPrototype;
    14.  
    15.     public static CubeSpawner Instance;
    16.     private void Awake() {
    17.         Instance = this;
    18.     }
    19.  
    20.     /*
    21.     // This commented out code does work fine, but spawning from a job causes the problem
    22.  
    23.     EntityManager entityManager;
    24.     public static EntityArchetype cubeArchetype;
    25.  
    26.     void Start()
    27.     {
    28.         entityManager = World.Active.GetOrCreateManager<EntityManager>();
    29.  
    30.         cubeArchetype = entityManager.CreateArchetype(
    31.             typeof(Cube),
    32.             typeof(Position),
    33.             typeof(Rotation),
    34.             typeof(MeshInstanceRenderer)
    35.         );
    36.     }
    37.  
    38.     void Update()
    39.     {
    40.         for( int i=0; i<100; i++ ) {
    41.             SpawnCube();
    42.         }
    43.     }
    44.  
    45.     void SpawnCube() {
    46.         // The CubeSystem will Destroy it after 2 seconds
    47.         var entity = entityManager.CreateEntity(cubeArchetype);
    48.         entityManager.SetComponentData(entity, new Cube {
    49.             DestroyTime = Time.time + 2
    50.         });
    51.         entityManager.SetComponentData(entity, new Position {
    52.             Value = Random.insideUnitSphere
    53.         });
    54.         entityManager.SetSharedComponentData(entity, CubeRendererPrototype.Value);
    55.     }
    56.     */
    57. }
    58.  
    59. public class CubeSpawnerSystem : JobComponentSystem {
    60.     class CubeSpawnerBarrier : BarrierSystem { }
    61.     [Inject] CubeSpawnerBarrier m_CubeSpawnerBarrier;
    62.  
    63.     public static EntityArchetype cubeArchetype;
    64.  
    65.     // This job just creates a new cube entity
    66.     struct CubeSpawnerJob : IJobParallelFor {
    67.         public float Time;
    68.         [DeallocateOnJobCompletion] [ReadOnly] public NativeArray<float3> RandomPositions;
    69.         [WriteOnly] public EntityCommandBuffer.Concurrent EntityCommandBuffer;
    70.         public void Execute( int i ) {
    71.             EntityCommandBuffer.CreateEntity(i, cubeArchetype);
    72.             EntityCommandBuffer.SetComponent(i, new Cube {
    73.                 DestroyTime = Time + 2
    74.             });
    75.             EntityCommandBuffer.SetComponent(i, new Position {
    76.                 Value = RandomPositions[i]
    77.             });
    78.             EntityCommandBuffer.SetSharedComponent(i, CubeSpawner.Instance.CubeRendererPrototype.Value );
    79.         }
    80.     }
    81.  
    82.     protected override void OnCreateManager() {
    83.         cubeArchetype = EntityManager.CreateArchetype(
    84.             typeof(Cube),
    85.             typeof(Position),
    86.             typeof(Rotation),
    87.             typeof(MeshInstanceRenderer)
    88.         );
    89.     }
    90.  
    91.     protected override JobHandle OnUpdate( JobHandle inputDeps ) {
    92.         // Run the job 100 times, to create 100 new cubes per frame
    93.         var randomPositions = new NativeArray<float3>(100, Allocator.TempJob);
    94.         for( int i = 0; i < 100; i++ )
    95.             randomPositions[i] = UnityEngine.Random.insideUnitSphere;
    96.  
    97.         // The CubeSystem will delete them after 2 seconds
    98.         var job = new CubeSpawnerJob {
    99.             Time = Time.time,
    100.             RandomPositions = randomPositions,
    101.             EntityCommandBuffer = m_CubeSpawnerBarrier.CreateCommandBuffer().ToConcurrent()
    102.         }.Schedule(100, 32, inputDeps);
    103.  
    104.         return job;
    105.     }
    106. }
    107.  

    CubeSystem.cs
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Rendering;
    5. using UnityEngine;
    6.  
    7. public struct Cube : IComponentData {
    8.     public float DestroyTime;
    9. }
    10.  
    11. // Just destroys the cube after 2 seconds, and tests to see if we can access the MeshInstanceRenderer
    12. public class CubeSystem : JobComponentSystem
    13. {
    14.     class CubeBarrier : BarrierSystem { }
    15.     [Inject] CubeBarrier m_CubeBarrier;
    16.  
    17.     struct CubeGroup {
    18.         readonly public int Length;
    19.         [ReadOnly] public ComponentDataArray<Cube> Cube;
    20.         [ReadOnly] public SharedComponentDataArray<MeshInstanceRenderer> MeshInstanceRenderer;
    21.         [ReadOnly] public EntityArray EntityArray;
    22.     }
    23.     [Inject] CubeGroup m_CubeGroup;
    24.  
    25.     struct CubeJob : IJobParallelFor {
    26.  
    27.         public float Time;
    28.         [ReadOnly] public ComponentDataArray<Cube> Cube;
    29.         [ReadOnly] public SharedComponentDataArray<MeshInstanceRenderer> MeshInstanceRenderer;
    30.         [ReadOnly] public EntityArray EntityArray;
    31.         [WriteOnly] public EntityCommandBuffer.Concurrent EntityCommandBuffer;
    32.  
    33.         public void Execute( int i ) {
    34.             var cube = Cube[i];
    35.             if( Time >= cube.DestroyTime )
    36.                 EntityCommandBuffer.DestroyEntity(i, EntityArray[i]);
    37.  
    38.             // This throws an error, comment it out ant things work fine
    39.             var meshInstanceRenderer = MeshInstanceRenderer[i];
    40.  
    41.             Debug.Log("Only gets here the first 100 times");
    42.         }
    43.     }
    44.  
    45.     protected override JobHandle OnUpdate( JobHandle inputDeps ) {
    46.  
    47.         var job = new CubeJob {
    48.             Time = Time.time,
    49.             Cube = m_CubeGroup.Cube,
    50.             MeshInstanceRenderer = m_CubeGroup.MeshInstanceRenderer,
    51.             EntityArray = m_CubeGroup.EntityArray,
    52.             EntityCommandBuffer = m_CubeBarrier.CreateCommandBuffer().ToConcurrent()
    53.         }.Schedule(m_CubeGroup.Length, 64, inputDeps);
    54.  
    55.         return job;
    56.     }
    57. }
    58.  
     
  29. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    - First of all you cannot [BurstCompile] OnUpdate code, it is only valid for job struct in which high performance C# subset is enforced.

    - Modifying the NativeArray is correct, it is connecting directly to ECS database.

    - MeshInstanceRenderer is a SharedComponentData and therefore it is not actually in the chunk. The chunk only contains an index to that shared data. The actual shared data is kept globally by EntityManager in a List<object>
    -- You can use this index with EntityManager.GetAllUnique... something and iterate with the index until you found the right item. Then because Mesh and Material is a reference type you can change it and have it reflect to all currently associated chunks.
    -- Or you could use EntityManager.SetSharedCompoenntData to some entities in which it will move to a different chunk plus EntityManager would create a new index entry for that new combination of SCD

    - You can put isReadOnly : true on GetArchetypeChunkComponentType<WorldMeshRenderBounds>(___) if you are not intended to write. That way it enables more concurrency possibility for your JobComponentSystem dep chain.
     
  30. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    @5argon answered faster than me :)
    Edit: sorry @Doug-Wolanick I'm missclick to message for reply :) I answer to you in next message
     
  31. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    You can't access SCD value directly inside job, because it's really not stored in chunk (read @5argon answer for understanding) you only can manipulate SCD index per chunk (SCD, as I say before, it's not per entity, but per chunk basis)
     
  32. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    I gave up with trying to get shared components to work with a job, because when I think I fixed it the error appears back after a couple of minutes of testing, I'll give it a try once I see more examples on how to do it.
    - Why I can't use BurstCompile if the attribute is marked to work with methods too on the class declaration?

    - Perfect, good to be sure about that one.

    - Will this be faster/better than using a ComponentSystem to update the data on the SharedComponentData? Right now the way I have it is that a JobComponentSystem is updating the animator (pretty much just updating the current frame [Which involves a couple calculations to get the correct frame]), then a ComponentSystem uses
    EntityManager.GetSharedComponentData
    and
    buffer.SetSharedComponent
    to update the material texture offset (it's a sprite sheet animation). So this ComponentSystem checks if it needs to update the material based on the current frame and changes the offset.

    - Gotcha, I didn't think about using that.
     
  33. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Alright so I got it working following what I said above, but now I'm wondering. How would I destroy an entity without it affecting the other systems?

    Let's say that I have 2 systems that are using the same entity. Then 1 destroys the entity, how can I avoid the other system to throw error because of the missing entity?
     
  34. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    It seems Burst is planned to be able to compile any method according to some comments but currently it take just the job type (your custom job struct type that implements IJob)

    Code (CSharp):
    1.         // TODO: This is not working with method. We have to change this
    2.         public static bool ExtractBurstCompilerOptions(Type type, out string optimizationFlags)
    3.         {
    I think you cannot update SharedComponentData in a job, it would involves accessing static and managed List from thread. Using ComponentSystem is the right approach to change SharedComponentData.


    If you are using inputDeps chain of JobComponentSystem correctly the other system will wait for prior system's destroy first (in the case that update order ended up later than the prior system) because it knows the reader-writer component type is the same. You must use [Inject] the barrier that queue the destroy command in prior system to let JobComponentSystem chain know that it would have to wait for barrier playback and not just the job code (which just enqueue destroy command)
     
  35. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    So if I have both systems [Inject] from the same barrier it will work? What about any Unity system like the mesh renderer, why it doesn't throw an error since I destroyed the entity?
     
  36. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    If you inject from the same barrier it means that enqueued commands from both ECB will playback together at the barrier's update step. The playback order is determined purely by which ECB got .CreateCommandBuffer first.

    You cannot destroy entity in the worker thread, at least you queued destroy commands in ECB and have it playback at the barrier. Barrier playback in the main thread.

    MeshInstanceRendererSystem is a ComponentSystem which query chunks to schedule jobs which completes in the system.

    If this barrier updates before MeshInstanceRendererSystem then the destroy result have already taken effect and may reduce queried chunks. If updates after then no problem. In both case it should not throw any error.
     
    Rotary-Heart likes this.
  37. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    I'm trying to implement a custom culler for the animator system. My main idea was to check if the entity has the Culler component, then if it does check the value to identify if it is being culled. I got it working without issues with the ComponentSystem, but I have no idea how to do it a JobComponentSystem.

    Here's how I'm doing it on the ComponentSystem:
    Code (CSharp):
    1. if (EntityManager.HasComponent<Culler>(entities[i]))
    2. {
    3.     Culler culler = EntityManager.GetComponentData<Culler>(entities[i]);
    4.  
    5.     if (culler.cullingState != Culler.Culling.None)
    6.         continue;
    7. }
    How can I implement something like this in a JobComponentSystem?
     
  38. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Found how to solve it and in the process made the above code faster. Good thing that my JobComponentSystem was already using chunks. Here's how I solved it:

    Code (CSharp):
    1. NativeArray<Culler> cullers = chunk.GetNativeArray(objectCullerType);
    2.  
    3. for (int i = 0; i < chunk.Count; i++)
    4. {
    5.     //If this chunk has the component and is culled
    6.     if (cullers.Length > 0 && cullers[i].cullingState != Culler.Culling.None)
    7.         continue;
    8.        
    9.     //Rest of the logic here
    10. }
    This works perfectly fine on both JobComponentSystem and ComponentSystem.