JobComponentSystem question

Lets say I am making an RTS with 20 thousand+ units active at any time.

I have figured that I can create entities that act as unit groups; they store shared data between units of that group. The player will interact with these groups instead of individual units, and then the units will do some very basic systems to remain coherent and in formation, etc.

Almost all logic/work needing to be done on these entities (except for player input and translation movement/animation) , including AI does not need to be done every frame. Ideally all of this work can be done withing every second, or within every 60 frames

What if I structures my code by having a seperate component tag for every group, something like:

public struct UnitGroup1: Icompdata
public struct UnitGroup2: Icompdata
Etc…

And then have a JobComponentSystem, with a differentJob struct for each one. During update, based on some int counter, I choose which one to schedule and run.

Performance-wise I see two major benefits.

#1 By having the work done on the units for each group in seperate systems, it makes the work highly multi-threadable.

#2 It allows me to divide up the pathfinding and logic between frames, because in each JobSystem I can just have some int counter and set it so that each job will run one frame after the other. Because I can just filter out all the entities by their tag.

I know for a fact that new groups and units will never be added during a game, because they will be set.

Lets say I have 60 groups per team with 300 units each. This means using my system instead of working on 30 * 300 units every frame, I can work on 1 group each frame meaning within 60 frames I will hit every unit , cutting my work down by 60 times.

This will lead to copy and paste of components and systems (60 times for every new system I want to implement), but I’m willing to do it, if there’s no other way to do this.

Use an ISharedComponentData with a unique value per group rather than tag components. That way, you don’t have to duplicate code.

1 Like

Here is an example JobComponentSystem using what I tried to describe.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Burst;
using Unity.Collections;

public struct Group_01 : IComponentData {}
public struct Group_02 : IComponentData {}
public struct Group_03 : IComponentData {}

public struct UnitData : IComponentData {}
public struct GroupData : IComponentData
{
    // Some info like a position which each unit in group will follow
    // Shape of formation
}

public class UnitPathFidningJobSystem : JobComponentSystem
{
    [BurstCompile] [RequireComponentTag(typeof(Group_01))]
    private struct Group01_Job : IJobForEach<UnitData>
    {
        [ReadOnly] public ComponentDataFromEntity<GroupData> groupData;

        public void Execute( ref UnitData unitData )
        {
            // Perform work on each unit within Group01,
            // Using groupData to access shared group info
            // So for example if in another system the player selects a group and tells it to move somewhere,
            // The groupentity will do some pathfinding and move each frame
            // Then in here well calculate where each unit of the group should move based on the
            // Group's position accesses from groupData and then UnityData has some index which will
            // Be used to calculate its position relative to the group's position so I can have nice
            // Formations
        }
    }

    [BurstCompile] [RequireComponentTag( typeof( Group_02 ) )]
    private struct Group02_Job : IJobForEach<UnitData>
    {
        [ReadOnly] public ComponentDataFromEntity<GroupData> groupData;

        public void Execute( ref UnitData unitData )
        {
            //Same thing but for another group
        }
    }

    [BurstCompile] [RequireComponentTag( typeof( Group_03 ) )]
    private struct Group03_Job : IJobForEach<UnitData>
    {
        [ReadOnly] public ComponentDataFromEntity<GroupData> groupData;

        public void Execute( ref UnitData unitData )
        {
            //Same thing but for another group
        }
    }

    /* =============================================== */

    private int frameCount = 0;

    protected override JobHandle OnUpdate( JobHandle inputDeps )
    {
        frameCount++;

        if ( frameCount > 60 )
        {
            frameCount = 0;
        }

        switch ( frameCount )
        {
            case 0:
            Group01_Job job1 = new Group01_Job { groupData = GetComponentDataFromEntity<GroupData>() };
            JobHandle jobHandle1 = job1.Schedule( this , inputDeps );
            return jobHandle1;
            case 1:
            Group02_Job job2 = new Group02_Job { groupData = GetComponentDataFromEntity<GroupData>() };
            JobHandle jobHandle2 = job2.Schedule( this , inputDeps );
            return jobHandle2;
            case 2:
            Group03_Job job3 = new Group03_Job { groupData = GetComponentDataFromEntity<GroupData>() };
            JobHandle jobHandle3 = job3.Schedule( this , inputDeps );
            return jobHandle3;
            // Continue to 60..
            default:
            return new JobHandle();
        }
    }
}

Thank you I didn’t know ISharedComponentData was used for this, Ill have to look into it, I have posted an example of the code I might have used, would ISharedComponentData nullify the example I’ve posted?

I don’t think I can use ISharedComponentData inside a job?

You can’t use it inside a job, but you can use it as a filter on an EntityQuery which you can use when scheduling a job instead of the the RequireComponentTagAttribute.

2 Likes