Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Moving from SystemBase to ISystem with IJobEntity

Discussion in 'Entity Component System' started by imaginadio, Jun 2, 2023.

  1. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    60
    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:
    Code (CSharp):
    1. partial class GameScoreSystem : SystemBase {
    2.     protected override void OnUpdate() {
    3.        
    4.         NativeArray<int> successOrdersNative = new NativeArray<int>(1, Allocator.TempJob);
    5.  
    6.         Entities
    7.             .ForEach((in CanDeliverMealsComponent canDeliverMeals) => {
    8.                 successOrdersNative[0] += canDeliverMeals.SuccessOrders;
    9.             }).Schedule();
    10.        
    11.         Entities
    12.             .ForEach((ref AnotherGameScoreComponent gameScore) => {
    13.                 gameScore.Score = successOrdersNative[0];
    14.             }).Schedule();
    15.        
    16.         Entities
    17.             .WithDisposeOnCompletion(successOrdersNative)
    18.             .ForEach((ref GameScoreComponent gameScore) => {
    19.                 gameScore.Score = successOrdersNative[0];
    20.             }).Schedule();
    21.     }
    22. }
    My questions is:
    1. 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?
    2. 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?
    3. 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()
    4. And additional question related to the code below: is it any difference between scheduling jobs these two ways?
    Code (CSharp):
    1.         new DisableOnPlayModeJob {Ecb = ecbParallel}.ScheduleParallel();
    2.         state.Dependency = new DisableOnPlayModeJob {Ecb = ecbParallel}.ScheduleParallel(state.Dependency);
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,223
    1) Use state.WorldUpdateAllocator and forget about disposing.
    2) Same as Entities.ForEach.
    3) Which data?
    4) No difference at all.
     
  3. PeppeJ2

    PeppeJ2

    Joined:
    May 13, 2014
    Posts:
    42
    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.
     
    imaginadio likes this.
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    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.
     
    imaginadio likes this.
  5. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    60
    thanx for answer

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

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,223
    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.
     
  7. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    198
    Hey @DreamingImLatios can you provide a code example using this for this situation?
     
  8. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    60
    toomasio and DreamingImLatios like this.
  9. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    60
    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:
    Code (CSharp):
    1. [BurstCompile]
    2. public partial struct DisableOnPlayModeSystem : ISystem {
    3. }
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,223
    It is no longer necessary on the ISystem struct itself for normal usage.
     
    bb8_1 and imaginadio like this.
  11. imaginadio

    imaginadio

    Joined:
    Apr 5, 2020
    Posts:
    60
    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:
    Code (CSharp):
    1. // System - source of input.
    2. partial class CustomInputSystem : SystemBase {
    3.     public event EventHandler OnInteractAction;
    4.     private CustomInputActions _customInputActions;
    5.  
    6.     protected override void OnCreate() {
    7.         _customInputActions = new CustomInputActions();
    8.         _customInputActions.Player.Enable();
    9.         _customInputActions.Player.Interact.performed += InteractOnperformed;
    10.     }
    11.  
    12.     private void InteractOnperformed(InputAction.CallbackContext obj) {
    13.         OnInteractAction?.Invoke(this, EventArgs.Empty);
    14.     }
    15. }
    16.  
    17. // System that want to process input events.
    18. partial class SelectedItemInteractSystem : SystemBase {
    19.  
    20.     protected override void OnCreate() {
    21.         var playerInputActionsSystem = this.World.GetOrCreateSystemManaged<CustomInputSystem>();
    22.         playerInputActionsSystem.OnInteractAction += InstanceOnOnInteractAction;
    23.     }
    24.  
    25.     private void InstanceOnOnInteractAction(object sender, EventArgs e) {
    26.         Entities.ForEach((Entity entity) => {
    27.                 // ... do some tasks
    28.             }).Schedule();
    29.     }
    30. }
    Here is how I tried to make the same with ISystem (source system is the same):
    Code (CSharp):
    1. // System that want to process input events.
    2. public partial struct JumpingSystem : ISystem {
    3.     public void OnCreate(ref SystemState state) {
    4.         var inputSystem = state.World.GetOrCreateSystemManaged<CustomInputSystem>();
    5.         inputSystem.OnInteractAction += InputSystemOnInteractAction;
    6.     }
    7.  
    8.     private void InputSystemOnInteractAction(object sender, EventArgs e) {
    9.         new SomeJob().Schedule();
    10.     }
    11. }
    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?
     
    bb8_1 likes this.
  12. gencontain

    gencontain

    Joined:
    Nov 15, 2012
    Posts:
    15
    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.
     
    bb8_1 and imaginadio like this.
  13. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    For this case I usually just add <InputData>. Poll inputs in a separate system that runs at the beginning of the frame. Then you can just query over <InputData> 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.
     
    Last edited: Jun 4, 2023
    bb8_1 and imaginadio like this.