Search Unity

Can I optimize this system?

Discussion in 'Entity Component System' started by frankfringe, Apr 7, 2019.

  1. frankfringe

    frankfringe

    Joined:
    Feb 9, 2019
    Posts:
    106
    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Mathematics;
    6. using static Unity.Mathematics.math;
    7. using Unity.Transforms;
    8. using UnityEngine; //Math4.max
    9.  
    10. // JobComponentSystems can run on worker threads.
    11. // However, creating and removing Entities can only be done on the main thread to prevent race conditions.
    12. // The system uses an EntityCommandBuffer to defer tasks that can't be done inside the Job.
    13. public class FindProfessionSystem : JobComponentSystem
    14. {
    15.     // EndSimulationBarrier is used to create a command buffer which will then be played back when that barrier system executes.
    16.     EndSimulationEntityCommandBufferSystem m_EntityCommandBufferSystem;
    17.     EntityQuery Professions;
    18.  
    19.     protected override void OnCreateManager()
    20.     {
    21.         // Cache the EndSimulationBarrier in a field, so we don't have to create it every frame
    22.         m_EntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    23.         Professions = GetEntityQuery(ComponentType.ReadOnly<ProfessionRequirements>());
    24.  
    25.     }
    26.  
    27.     [ExcludeComponent(typeof(RobotJob))]
    28.     struct FindProfessionJob : IJobForEachWithEntity<RobotPropertiesComponent>
    29.     {
    30.         public EntityCommandBuffer CommandBuffer;
    31.         [DeallocateOnJobCompletion] public NativeArray<ProfessionRequirements> ProfessionRequirements;
    32.         [DeallocateOnJobCompletion] public NativeArray<Entity> Professions;
    33.         [DeallocateOnJobCompletion] public NativeArray<float> BestUsePerTimes;
    34.  
    35.         public void Execute(Entity entity, int index, [ReadOnly] ref RobotPropertiesComponent properties)
    36.         {
    37.             var bestUseTimeForThisEntity = 0f;
    38.             Entity bestProfession = Professions[0];
    39.             for (int i = 0; i < Professions.Length; i++)
    40.             {
    41.                 var professionSkill = dot(ProfessionRequirements[i].Value, properties.Value);
    42.                 float use = BestUsePerTimes[i] * (1 - 0.5f * professionSkill);
    43.                 if (use > bestUseTimeForThisEntity)
    44.                 {
    45.                     bestUseTimeForThisEntity = use;
    46.                     bestProfession = Professions[i];
    47.                 }
    48.             }
    49.             CommandBuffer.AddComponent(entity, new RobotJob { Job = bestProfession });
    50.         }
    51.     }
    52.  
    53.     public float goodUse(Entity good)
    54.     {
    55.         var good_data = EntityManager.GetComponentData<Good>(good);
    56.         return Mathf.Max(good_data.Nutrition, good_data.Taxes);
    57.     }
    58.  
    59.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    60.     {
    61.         //Instead of performing structural changes directly, a Job can add a command to an EntityCommandBuffer to perform such changes on the main thread after the Job has finished.
    62.         //Command buffers allow you to perform any, potentially costly, calculations on a worker thread, while queuing up the actual insertions and deletions for later.
    63.  
    64.         // Schedule the job that will add Instantiate commands to the EntityCommandBuffer.
    65.         var professions = Professions.ToEntityArray(Allocator.TempJob);
    66.         var professions_requirements = Professions.ToComponentDataArray<ProfessionRequirements>(Allocator.TempJob);
    67.         var dummy = new float[professions.Length]; //default contains only 0s
    68.         var bestUsePerTimeArray = new NativeArray<float>(dummy, Allocator.TempJob);
    69.         var goodProductionBuffers = GetBufferFromEntity<GoodProductionBufferElement>();
    70.         for (int i = 0; i < professions.Length; i++)
    71.         {
    72.             var professionEntity = professions[i];
    73.             var goodsBuffer = goodProductionBuffers[professionEntity];
    74.             float bestUsePerTime = 0;
    75.             for (int j = 0; j < goodsBuffer.Length; j++)
    76.             {
    77.                 GoodProduction goodProduction = goodsBuffer[j];
    78.                 var use = goodUse(goodProduction.Good);
    79.                 bestUsePerTime = Mathf.Max(bestUsePerTime, use / goodProduction.Duration);
    80.             }
    81.             bestUsePerTimeArray[i] = bestUsePerTime;
    82.         }
    83.  
    84.         var job = new FindProfessionJob
    85.         {
    86.             CommandBuffer = m_EntityCommandBufferSystem.CreateCommandBuffer(),
    87.             Professions = professions,
    88.             ProfessionRequirements = professions_requirements,
    89.             BestUsePerTimes = bestUsePerTimeArray,
    90.         }.ScheduleSingle(this, inputDeps);
    91.  
    92.  
    93.         // SpawnJob runs in parallel with no sync point until the barrier system executes.
    94.         // When the barrier system executes we want to complete the SpawnJob and then play back the commands (Creating the entities and placing them).
    95.         // We need to tell the barrier system which job it needs to complete before it can play back the commands.
    96.         m_EntityCommandBufferSystem.AddJobHandleForProducer(job);
    97.  
    98.         return job;
    99.     }
    100. }

    This is my system that finds a profession for every robot that does not have a profession yet. Most of the time all the robots have a profession (something like cook etc.). But the system is still run instead of being inactive because of the logic in `OnUpdate`. Can I somehow rewrite the system s.t. it only runs when there are Robots without a Profession? Are there other ways to optimize the above code?
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,779
    You can add above Job FindProfessionJob [RequireComponentTag ( typeof (type) )], to ensure, only entities with given component executes.

    I would rather create NoProfessionTag component, rather than adding component RobotJob wit data.
    Tags without data are treated differently.
    And you will set just value to component.
    Then when adding profession, simply remove tag component.

    Try encapsulate as much in jobs. You can have in your case main job, without ECB and [BurstCompiled], and separate job with ECB, just to remove tags.

    You can also fetch group of selected no profession robots in OnUpdate, then if all robots have profession, then system will simply halt.

    Multiple options. And these are just some.
     
  3. frankfringe

    frankfringe

    Joined:
    Feb 9, 2019
    Posts:
    106
    Thank you for the answer. Where can I read about how components without content are treated differently?
     
  4. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,779
    It was mentioned quite while ago, here on forum (somewhere).
    Not sure if any official documentation discuses this subject.
    So unfortunately, I don't have a link, as I would need dig through tons of posts, to attempt finding it. Sorry.

    Also, while ECS evolved since then quite a bit, I assume, this is still true holding.
     
    Last edited: Apr 7, 2019
  5. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Adding the [BurstCompile] attribute should give you a 10-20x speedup.

    I see you are using AddComponent, which currently isn't yet supported with Burst. So you could try to build a NativeArray of commands that a second job Adds as a component using EntityCommandBuffer. This way the first job can be bursted.
     
    Antypodish and alexnown like this.
  6. Deleted User

    Deleted User

    Guest

    Any ETA on Burst support for such commands?
     
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    No ETA afaik. But it will some day as @xoofx (or @Joachim_Ante I’m really don’t remember ) mentioned in other thread on this forum
     
    GilCat and Deleted User like this.
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,779
    Well, at least for now we can set values without ECB :)