Can I optimize this system?

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Unity.Transforms;
using UnityEngine; //Math4.max

// JobComponentSystems can run on worker threads.
// However, creating and removing Entities can only be done on the main thread to prevent race conditions.
// The system uses an EntityCommandBuffer to defer tasks that can't be done inside the Job.
public class FindProfessionSystem : JobComponentSystem
{
    // EndSimulationBarrier is used to create a command buffer which will then be played back when that barrier system executes.
    EndSimulationEntityCommandBufferSystem m_EntityCommandBufferSystem;
    EntityQuery Professions;

    protected override void OnCreateManager()
    {
        // Cache the EndSimulationBarrier in a field, so we don't have to create it every frame
        m_EntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
        Professions = GetEntityQuery(ComponentType.ReadOnly<ProfessionRequirements>());

    }

    [ExcludeComponent(typeof(RobotJob))]
    struct FindProfessionJob : IJobForEachWithEntity<RobotPropertiesComponent>
    {
        public EntityCommandBuffer CommandBuffer;
        [DeallocateOnJobCompletion] public NativeArray<ProfessionRequirements> ProfessionRequirements;
        [DeallocateOnJobCompletion] public NativeArray<Entity> Professions;
        [DeallocateOnJobCompletion] public NativeArray<float> BestUsePerTimes;

        public void Execute(Entity entity, int index, [ReadOnly] ref RobotPropertiesComponent properties)
        {
            var bestUseTimeForThisEntity = 0f;
            Entity bestProfession = Professions[0];
            for (int i = 0; i < Professions.Length; i++)
            {
                var professionSkill = dot(ProfessionRequirements[i].Value, properties.Value);
                float use = BestUsePerTimes[i] * (1 - 0.5f * professionSkill);
                if (use > bestUseTimeForThisEntity)
                {
                    bestUseTimeForThisEntity = use;
                    bestProfession = Professions[i];
                }
            }
            CommandBuffer.AddComponent(entity, new RobotJob { Job = bestProfession });
        }
    }

    public float goodUse(Entity good)
    {
        var good_data = EntityManager.GetComponentData<Good>(good);
        return Mathf.Max(good_data.Nutrition, good_data.Taxes);
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        //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.
        //Command buffers allow you to perform any, potentially costly, calculations on a worker thread, while queuing up the actual insertions and deletions for later.

        // Schedule the job that will add Instantiate commands to the EntityCommandBuffer.
        var professions = Professions.ToEntityArray(Allocator.TempJob);
        var professions_requirements = Professions.ToComponentDataArray<ProfessionRequirements>(Allocator.TempJob);
        var dummy = new float[professions.Length]; //default contains only 0s
        var bestUsePerTimeArray = new NativeArray<float>(dummy, Allocator.TempJob);
        var goodProductionBuffers = GetBufferFromEntity<GoodProductionBufferElement>();
        for (int i = 0; i < professions.Length; i++)
        {
            var professionEntity = professions[i];
            var goodsBuffer = goodProductionBuffers[professionEntity];
            float bestUsePerTime = 0;
            for (int j = 0; j < goodsBuffer.Length; j++)
            {
                GoodProduction goodProduction = goodsBuffer[j];
                var use = goodUse(goodProduction.Good);
                bestUsePerTime = Mathf.Max(bestUsePerTime, use / goodProduction.Duration);
            }
            bestUsePerTimeArray[i] = bestUsePerTime;
        }

        var job = new FindProfessionJob
        {
            CommandBuffer = m_EntityCommandBufferSystem.CreateCommandBuffer(),
            Professions = professions,
            ProfessionRequirements = professions_requirements,
            BestUsePerTimes = bestUsePerTimeArray,
        }.ScheduleSingle(this, inputDeps);


        // SpawnJob runs in parallel with no sync point until the barrier system executes.
        // When the barrier system executes we want to complete the SpawnJob and then play back the commands (Creating the entities and placing them).
        // We need to tell the barrier system which job it needs to complete before it can play back the commands.
        m_EntityCommandBufferSystem.AddJobHandleForProducer(job);

        return job;
    }
}

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?

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.

Thank you for the answer. Where can I read about how components without content are treated differently?

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.

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.

2 Likes

Any ETA on Burst support for such commands?

No ETA afaik. But it will some day as @xoofx (or @Joachim_Ante_1 I’m really don’t remember ) mentioned in other thread on this forum

2 Likes

Well, at least for now we can set values without ECB :slight_smile: