Moving from SystemBase to ISystem with IJobEntity

Hello guys. I’m trying to move from SystemBase with Entities.ForEach() to ISystem with IJobEntity to improve performance, but I don’t understand some basic principles.
Here is code example of what I have in SystemBase:

partial class GameScoreSystem : SystemBase {
    protected override void OnUpdate() {
       
        NativeArray<int> successOrdersNative = new NativeArray<int>(1, Allocator.TempJob);

        Entities
            .ForEach((in CanDeliverMealsComponent canDeliverMeals) => {
                successOrdersNative[0] += canDeliverMeals.SuccessOrders;
            }).Schedule();
       
        Entities
            .ForEach((ref AnotherGameScoreComponent gameScore) => {
                gameScore.Score = successOrdersNative[0];
            }).Schedule();
       
        Entities
            .WithDisposeOnCompletion(successOrdersNative)
            .ForEach((ref GameScoreComponent gameScore) => {
                gameScore.Score = successOrdersNative[0];
            }).Schedule();
    }
}

My questions is:

  • How to make the same jobs in ISystem and IJobEntity? I mean how to dispose native array in the end, when the last job is completed?
  • How to control the order of execution in ISystem this way: first executes job, that fills the array, then 2 jobs that reads from array work independently in parallel and not waiting each other?
  • Is it any profit to get the initial value this way (using native array in job) in comparison with just getting this data in the main thread from singleton using SystemApi.GetSingleton()
  1. And additional question related to the code below: is it any difference between scheduling jobs these two ways?
        new DisableOnPlayModeJob {Ecb = ecbParallel}.ScheduleParallel();
        state.Dependency = new DisableOnPlayModeJob {Ecb = ecbParallel}.ScheduleParallel(state.Dependency);
  1. Use state.WorldUpdateAllocator and forget about disposing.
  2. Same as Entities.ForEach.
  3. Which data?
  4. No difference at all.

Also if you just want a single value sent between jobs you could consider using NativeReference instead of NativeArray. NativeReference is works exactly like NativeArray with Length of 1, just that you use .Value instead of [0] to access the value.

As for the first, you can also do disposal via .Dispose(jobHandle).
[JobHandle is returned by .Schedule / .ScheduleParallel]

That will dispose native collection after job chain is done.

thanx for answer

  1. I never do this in Entities.ForEach, so I don’t understand how it works.
    I guess it something like this?
var mainJob = new MainJobThatWriteToNativeArray {}.Schedule(state.Dependency);
new SideJob1ThatReadFromNativeArray { }.Schedule(mainJob);
new SideJob2ThatReadFromNativeArray { }.Schedule(mainJob);
  1. for example common game settings, like some floats that can be changed during playing (so BlobAsset is not appropriate for this)

Just make sure you do something with those two JobHandles in those two separate jobs when you are done.

Okay. I think I finally understand your question. And the answer is “it depends”. Was the data written on the main thread, or in a job? Are you reading the data in multiple scopes, or do you have the ability to cache it? Both your current approach and alternative are optimal in different situations. And a third approach would be to get the singleton entity and then do a lookup in the job.

Hey @DreamingImLatios can you provide a code example using this for this situation?

Here is the example https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/EntitiesSamples/Assets/ExampleCode/Jobs.cs

Btw I always see examples where [BurstCompile] is used above ISystem methods and jobs, but not used above ISystem itself. There is no profit from this? I mean like this:

[BurstCompile]
public partial struct DisableOnPlayModeSystem : ISystem {
}

It is no longer necessary on the ISystem struct itself for normal usage.

Another one question about ISystem: how to process events using ISystem that comes from another system through System.EventHandler? Here is my working implementation using SystemBase:

// System - source of input.
partial class CustomInputSystem : SystemBase {
    public event EventHandler OnInteractAction;
    private CustomInputActions _customInputActions;
  
    protected override void OnCreate() {
        _customInputActions = new CustomInputActions();
        _customInputActions.Player.Enable();
        _customInputActions.Player.Interact.performed += InteractOnperformed;
    }

    private void InteractOnperformed(InputAction.CallbackContext obj) {
        OnInteractAction?.Invoke(this, EventArgs.Empty);
    }
}

// System that want to process input events.
partial class SelectedItemInteractSystem : SystemBase {
  
    protected override void OnCreate() {
        var playerInputActionsSystem = this.World.GetOrCreateSystemManaged<CustomInputSystem>();
        playerInputActionsSystem.OnInteractAction += InstanceOnOnInteractAction;
    }

    private void InstanceOnOnInteractAction(object sender, EventArgs e) {
        Entities.ForEach((Entity entity) => {
                // ... do some tasks
            }).Schedule();
    }
}

Here is how I tried to make the same with ISystem (source system is the same):

// System that want to process input events.
public partial struct JumpingSystem : ISystem {
    public void OnCreate(ref SystemState state) {
        var inputSystem = state.World.GetOrCreateSystemManaged<CustomInputSystem>();
        inputSystem.OnInteractAction += InputSystemOnInteractAction;
    }

    private void InputSystemOnInteractAction(object sender, EventArgs e) {
        new SomeJob().Schedule();
    }
}

I got an error: No reference to SystemState was found for function with IJobEntity access, add ref SystemState ... as method parameter

I don’t understand how to pass SystemState in this case?

Trying to combine managed event handlers with an unmanaged ISystem is going to lead to problems such as this, I don’t recommend doing it. A better solution would be to write the event data into a NativeList (or any other suitable container) on the managed side and consume the native container in the unmanaged system.

For this case I usually just add . Poll inputs in a separate system that runs at the beginning of the frame. Then you can just query over in any system you need.

Events are actually anti-pattern when using ECS / DOD [on Entity level of granularity].
Its data you’re transferring / transforming. So process that data once and use results later on.