Will job system API improve? (boilerplate)

I’m trying out the ECS again. It says in the docs that ComponentSystem is only intended for prototyping and such, and that we should use job systems instead.

I’m making my first basic systems for this project and ComponentSystem API looks rather fine. But the job system is incredibly bloated with boilerplate code.

Actual code here is nonsense, just to test the API, but it’s based on the real thing I’m trying to do: operate camera position based on input and control settings.

Am I doing the job system right? Is there a way to decrease the amount of code? (I tried single IJob it wasn’t better) Will API be improved to make these simple tasks easier?

I’m doing the same thing with “CameraPivot” and “CameraHolder” but imagine that I’m not (in real code it won’t be)

public class CameraControlSystem : ComponentSystem
{
    protected override void OnUpdate()
    {
        var input = Entities.WithAll<MainDirectionalInput>()
                    .ToEntityQuery().GetSingleton<MainDirectionalInput>();
        var control = Entities.WithAll<MainCameraControl>()
                    .ToEntityQuery().GetSingleton<MainCameraControl>();
        Entities.WithAll<CameraHolder>()
        .ForEach((Entity e, ref Translation pos, ref Rotation rot) =>
        {
            if (input.Forward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * control.HorizontalVelocity * Time.deltaTime;
            }
            if (input.Backward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * control.HorizontalVelocity * Time.deltaTime;
            }
        });
        Entities.WithAll<CameraPivot>()
        .ForEach((Entity e, ref Translation pos, ref Rotation rot) =>
        {
            if (input.Forward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * control.HorizontalVelocity * Time.deltaTime;
            }
            if (input.Backward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * control.HorizontalVelocity * Time.deltaTime;
            }
        });
    }
}

Just comparing to MB approach. ~25% less symbols than ComponentSystem.

public class MonobehaviourApproach : Monobehaviour
{
    [SerializeField] private MainDirectionalInput input;
    [SerializeField] private MainCameraControl control;
    [SerializeField] private Transform _pivotTransform;
    [SerializeField] private Transform _cameraTransform;

    private void Update()
    {
        if (input.Forward.IsHold)
        {
            _pivotTransform.position += _pivotTransform.forward
                * control.HorizontalVelocity * Time.deltaTime;
        }
        if (input.Backward.IsHold)
        {
            _pivotTransform.position += _pivotTransform.forward
                * control.HorizontalVelocity * Time.deltaTime;
        }
        if (input.Forward.IsHold)
        {
            _cameraTransform.position += _pivotTransform.forward
                * control.HorizontalVelocity * Time.deltaTime;
        }
        if (input.Backward.IsHold)
        {
            _cameraTransform.position += _pivotTransform.forward
                * control.HorizontalVelocity * Time.deltaTime;
        }
    }
}

Job system requires ~100% more lines of code. The boilerplate ratio is insane for simple tasks that require data from multiple different entities.

public class CameraControlJobSystem : JobComponentSystem
{
    private EntityQuery _inputQuery;
    private EntityQuery _controlQuery;

    protected override void OnCreateManager()
    {
        _inputQuery = GetEntityQuery(new ComponentType[]
            { ComponentType.ReadOnly<MainDirectionalInput>() });
        _controlQuery = GetEntityQuery(new ComponentType[]
            { ComponentType.ReadOnly<MainCameraControl>() });
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        return new MoveJob
        {
            DeltaTime = Time.deltaTime,
            Input = _inputQuery.GetSingleton<MainDirectionalInput>(),
            Control = _controlQuery.GetSingleton<MainCameraControl>(),
        }.Schedule(this, new ZoomJob
        {
            DeltaTime = Time.deltaTime,
            Input = _inputQuery.GetSingleton<MainDirectionalInput>(),
            Control = _controlQuery.GetSingleton<MainCameraControl>(),
        }.Schedule(this, inputDeps));
    }

    [BurstCompile]
    [RequireComponentTag(typeof(CameraPivot))]
    protected struct MoveJob : IJobForEach<Translation, Rotation>
    {
        public MainDirectionalInput Input;
        public MainCameraControl Control;
        public float DeltaTime;

        public void Execute(ref Translation pos, ref Rotation rot)
        {
            if (Input.Forward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * Control.HorizontalVelocity * Time.deltaTime;
            }
            if (Input.Backward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * Control.HorizontalVelocity * Time.deltaTime;
            }
        }
    }

    [BurstCompile]
    [RequireComponentTag(typeof(CameraHolder))]
    protected struct ZoomJob : IJobForEach<Translation, Rotation>
    {
        public MainDirectionalInput Input;
        public MainCameraControl Control;
        public float DeltaTime;

        public void Execute(ref Translation pos, ref Rotation rot)
        {
            if (Input.Forward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * Control.HorizontalVelocity * Time.deltaTime;
            }
            if (Input.Backward.IsHold)
            {
                pos.Value += math.forward(rot.Value)
                    * Control.HorizontalVelocity * Time.deltaTime;
            }
        }
    }
}
1 Like

Main goal of ECS is not creating shorter code. In many cases there will be more code and more files. Of course if it is possible to remove boilerplate it should be but not at cost of performance.

1 Like

Yes, it will.

1 Like

I was excited about the promise of API nearly identical to the MonoBehaviours in usability or rather amount of code.

Can a Job be a lambda expression?

 public class EnemyDamageSystem : JobComponentSystem
    {
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            return Jobs.WithAll<Enemy, Red>().ForEach((ref Health health, ref Damage damage) =>
            {
                health -= damage;
            }).Schedule(this, inputDeps);
        }
    }

We are still planning to expose Lambda based job API.
This requires codegen, which first requires building a C# compiler that lets us plug in to the codegen.
I expect that to land sometime towards the end of the year-ish.

3 Likes

FYI.

GetSingleton(); is a thing, no need for the crazy query logic around it…

2 Likes

Any chance this will be Roslyn based and have extension points? That would be fun.

Does land mean preview or release?

Is the plan to have better access control and typing when that lands? Things like attributes for denoting readonly make the current API quite a pain compared to idiomatic C# where that’s an immediate compiler error.

I’m really excited for this.

1 Like

Ah, found it, thanks. I was looking in EntityManager.GetSingleton<>(); Entities.GetSingleton<>(); before.

(EDIT: Also this means I CAN get singleton directly in a job system. Cool.)

Very often I need a single component from a query, so something like that would be nice:

var pivotMatrix = Entities.WithAll<MainCamera, Pivot, LocalToWorld>().GetComponent<LocalToWorld>();

or

var pivotMatrix = GetComponent<LocalToWorld>(GetEntity<MainCamera, Pivot, LocalToWorld>());

Because I know that only one entity with (MainCamera, Pivot, LocalToWorld) exist. I hoped that singletons would work like that.

The line 51 won’t work? (Getting singleton from an entity with multiple components?)
And the line 52 won’t deallocate the array I assume?

I’m looking for the best way to grab a single entity, in one line of code preferably.

The JobSystem looks more manageable but of course with extra hoops to jump through to pass the data to the job and then use it in the job.

Really looking forward to lambda jobs.