Search Unity

Question Why is my job staying on the Main thread when using ScheduleParallel

Discussion in 'Entity Component System' started by ArnaudJopart, Jun 23, 2022.

  1. ArnaudJopart

    ArnaudJopart

    Joined:
    Oct 27, 2016
    Posts:
    13
    Hello, dear DOTS community.

    I'm exploring DOTS for a while and came across an issue I need help to understand.
    The following system detects, for each player entity, enemy entites closer than 5 Units. A dynamic buffer this then filled with the reachable targets.

    The system is working fine, but the job stays on the Main Thread, even when using ScheduleParallel.
    I would explain that by the small number of players (6), but no matter how much player I add, the only impact is my frame rate tanking. Must this kind of system stay on the main thread?

    I would be more than happy to have your feedback on this subject.
    Thanks a lot for your help.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using Components;
    4. using Unity.Collections;
    5. using Unity.Entities;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8. using UnityEngine;
    9.  
    10. public partial class DetectMultipleNearestEnemySystem_JobChunk_Schedule : SystemBase
    11. {
    12.     private EntityQueryDesc m_queryDescription;
    13.     private EntityQuery m_entityQuery;
    14.    
    15.     protected override void OnStartRunning()
    16.     {
    17.         m_queryDescription = new EntityQueryDesc
    18.         {
    19.             None = new ComponentType[] { typeof(AlreadyTargeted), typeof(SingleTargetComponent) },
    20.             All = new ComponentType[]{ typeof(EnemyTagComponent) },
    21.         };
    22.        
    23.         m_entityQuery = GetEntityQuery(m_queryDescription);
    24.     }
    25.     protected override void OnUpdate()
    26.     {
    27.         var translationTypeHandle = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentTypeHandle<Translation>(true);
    28.         var archetypeChunkArray = m_entityQuery.CreateArchetypeChunkArray(Allocator.TempJob);
    29.         var entityTypeHandle = GetEntityTypeHandle();
    30.  
    31.         Entities.
    32.             WithBurst().
    33.             WithAll<PlayerTagComponent>().
    34.             WithReadOnly(translationTypeHandle).
    35.             WithReadOnly(entityTypeHandle).
    36.             WithReadOnly(archetypeChunkArray).
    37.             ForEach(
    38.                 (ref DynamicBuffer<TargetItem> _targets, in Translation _localToWorld) =>
    39.                 {
    40.                     _targets.Clear();
    41.                    
    42.                     for (var i = 0; i < archetypeChunkArray.Length; i++)
    43.                     {
    44.                         var chunk = archetypeChunkArray[i];
    45.                         var nativeArrayOfEntities = chunk.GetNativeArray(entityTypeHandle);
    46.                         var nativeArrayOfTranslations = chunk.GetNativeArray(translationTypeHandle);
    47.  
    48.                         for (var j = 0; j < chunk.Count; j++)
    49.                         {
    50.                             var distance = math.distancesq(_localToWorld.Value, nativeArrayOfTranslations[j].Value);
    51.                             if (distance > 25) continue;
    52.                             {
    53.                                 _targets.Add(new TargetItem
    54.                                 {
    55.                                     m_entity = nativeArrayOfEntities[j]
    56.                                 });
    57.                             }
    58.                         }
    59.                     }
    60.                 }).
    61.             WithDisposeOnCompletion(translationTypeHandle).
    62.             WithDisposeOnCompletion(entityTypeHandle).
    63.             WithDisposeOnCompletion(archetypeChunkArray).
    64.             ScheduleParallel();
    65.     }
    66. }
    67.  
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Can you show profiler
     
    Anthiese likes this.
  3. ArnaudJopart

    ArnaudJopart

    Joined:
    Oct 27, 2016
    Posts:
    13
    Of course. Here it is, attached to this reply.
     

    Attached Files:

  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Based on the profiler, the main thread is being treated as a temporary worker thread to help out because the EntityCommandBufferSystem wants that job done ASAP.

    What your picture does not show is what the other worker threads are doing.
     
    elliotc-unity and Anthiese like this.
  5. ArnaudJopart

    ArnaudJopart

    Joined:
    Oct 27, 2016
    Posts:
    13
    Hi.
    Here is a more detailed snapshot of the profiler.
    In which conditions would the EntityCommandBufferSystem want to complete a job asap?
    I have another system instantiating projectiles based on the dynamicbuffer content. Thanks for the time you spend helping me, guys.

    Code (CSharp):
    1. using System.Net;
    2. using Components;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5. using Unity.Transforms;
    6.  
    7. namespace Systems
    8. {
    9.     public partial class FireMultipleProjectilesTowardTargetsSystem_Schedule : SystemBase
    10.     {
    11.         private EndSimulationEntityCommandBufferSystem m_ecbs;
    12.  
    13.         protected override void OnCreate()
    14.         {
    15.             m_ecbs = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    16.         }
    17.  
    18.         protected override void OnUpdate()
    19.         {
    20.             var ecb = m_ecbs.CreateCommandBuffer().AsParallelWriter();
    21.             var deltaTime = Time.DeltaTime;
    22.            
    23.             var localToWorldFromEntity = GetComponentDataFromEntity<LocalToWorld>(true);
    24.             var alreadyTargetedList = GetComponentDataFromEntity<AlreadyTargeted>(true);
    25.  
    26.             Entities
    27.                 .WithReadOnly(localToWorldFromEntity)
    28.                 .WithReadOnly(alreadyTargetedList)
    29.                 .WithAll<PlayerTagComponent, MultiTargetTagComponent>()
    30.                 .ForEach((int entityInQueryIndex,ref FireWeaponComponent _weaponData, ref DynamicBuffer<TargetItem> _targets, in Translation _translation) =>
    31.             {
    32.                 if (_weaponData.m_currentRate > 0)
    33.                 {
    34.                     _weaponData.m_currentRate -= deltaTime;
    35.                     return;
    36.                 }
    37.  
    38.                 _weaponData.m_currentRate = _weaponData.m_fireRate;
    39.                
    40.                 for (var i = 0; i < _targets.Length; i++)
    41.                 {
    42.                     if (alreadyTargetedList.HasComponent(_targets[i].m_entity)) continue;
    43.                    
    44.                     var projectile  = ecb.Instantiate(entityInQueryIndex, _weaponData.m_projectileEntityPrefab);
    45.                     ecb.SetComponent(entityInQueryIndex,projectile, new Translation
    46.                     {
    47.                         Value = _translation.Value+new float3(0,.5f,0)
    48.                     });
    49.                     ecb.AddComponent(entityInQueryIndex,projectile, new TargetComponent
    50.                     {
    51.                         m_entity = _targets[i].m_entity
    52.                     });
    53.                     var targetPosition = localToWorldFromEntity[_targets[i].m_entity];
    54.                     var direction = math.normalize(targetPosition.Position - _translation.Value);
    55.                    
    56.                     ecb.AddComponent(entityInQueryIndex, projectile, new MoveDirectionComponent
    57.                     {
    58.                         m_value = direction
    59.                     });
    60.                    
    61.                     ecb.AddComponent(entityInQueryIndex, _targets[i].m_entity, new AlreadyTargeted()
    62.                     {
    63.                     });
    64.                    
    65.                 }
    66.             }).
    67.                 WithDisposeOnCompletion(localToWorldFromEntity).
    68.                 WithDisposeOnCompletion(alreadyTargetedList).
    69.                 ScheduleParallel();
    70.            
    71.             m_ecbs.AddJobHandleForProducer(Dependency);
    72.         }
    73.     }
    74.  
    75.     public struct TargetComponent: IComponentData
    76.     {
    77.         public Entity m_entity;
    78.     }
    79. }
    80.  
     

    Attached Files:

  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    m_ecbs.AddJobHandleForProducer(Dependency);

    This here makes the ECBS wait on the job to complete.
     
  7. ArnaudJopart

    ArnaudJopart

    Joined:
    Oct 27, 2016
    Posts:
    13
    Ok, understood. Thanks a lot.
    I need this entityCommandBuffer to instantiate my projectiles but it's irrelevant to wait each frame for the completion of the detection job if I only create projectiles each .5f seconds (fire rate of the player).

    How could I then condition the execution of "m_ecbs.AddJobHandleForProducer(Dependency)" for when it's needed? This line is outside the Entities.ForEach loop, so it will execute each frame, no matter what happens in the loop. I would instead need a way to stop the execution of the System if not needed.
    I recall a system would stop running if no entity is found by the EntityQuery hidden in the Entities.ForEach(). Setting a special "AllowsFireTag" on player ready to shoot and checking for that tag in the FireSystem may work.

    But setting tags require another EntityCommandBuffer. Hum... I'm puzzled.