Search Unity

Best way to handle asynchronously running job systems?

Discussion in 'C# Job System' started by zachlindblad, Aug 1, 2019.

  1. zachlindblad

    zachlindblad

    Joined:
    Sep 29, 2016
    Posts:
    39
    So at the moment I have a pretty heavy jobComponentsystem that sometimes runs longer than my goal frametime. In reality I don't absolutely need the result of this system displayed within the same frame to the player, but I don't know if there's a clean way to allow a system to run longer than one frame, and prevent it from starting again until the previous iteration completes.

    I can kindof hack what I want together using coroutines, or maybe a flag in a singleton entity that I post to via entityCommandBuffer from some final cleanup job in my system, but both of these "smell" like I'm doing something wrong here. I just wanted to see if someone had a better way of handling this kind of behavior.

    Ideally I know the right call would be to break this into multiple systems, but it's a single graph traversal job that's taking up most of my bandwidth right now, and I don't see any way to break that up into smaller jobs cleanly in a way that wouldn't have a big performance hit.

    The only thread I could find in my search about this was:
    https://forum.unity.com/threads/multiple-frame-job.648877/

    Which is specifically about making a system that runs every N frames, where I want to immediately start another iteration after the previous completes.

    Anyone run into this sort of use case before?
     
  2. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    Jobs arent required to run and complete in one frame, they can run as long as you want them to. To have a job run and then stop, I just set a counter in the Onupdate method, and then set this.enabled = false after the counter reaches the number of iterations that I want.

    Code (CSharp):
    1. counter++;
    2.         if (counter >= 5)
    3.         {
    4.             this.Enabled = false;
    5.         }
     
  3. zachlindblad

    zachlindblad

    Joined:
    Sep 29, 2016
    Posts:
    39
    Yeah, as I mentioned at the bottom of my post, I saw in that thread I linked that I can run it every N frames, but this system can take anywhere from almost no time to about 3 frames, and I have no idea how many frames that would be on different hardware, or if the user wants an unlocked fps.
    I want changes to be displayed to the user as soon as possible, so I want this system to run again as soon as it has completed, rather than waiting extra time it doesn't need to. I can replace the counter with a flag read from an entity's component data, but I didn't know if there was a cleaner way to do that
     
  4. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    I've never seen that thread above so its news to me. The job will run again automatically once its finished unless you specify, it goes into the Onupdate again only when the job or jobs have finished. That code above will run my job 5 times no matter how long they take(I think, I haven tested it fully yet).
     
  5. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Have your jobs set a value when it's done. If it's done call Complete and schedule it again if needed or whatever.

    Just use a NativeArray<bool> for example with a length of 1 for communicating between job and main thread.
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    First things first, you need to make sure that your long-running jobs either are not an ECS type job (IJobForEach, using ComponentDataFromEntity, ect) or use an alternate world you can manually tick slower than the framerate. Using ToComponentDataArray is fine.

    Next, schedule the long-running job, but don't call complete on it, don't combine it with any other jobs, and don't return it if you are scheduling from a JobComponentSystem. Return the inputDeps JobHandle you feed into your long-running job instead.

    Store the long-running job somewhere that gets ticked every frame (or multiple times per frame if necessary). On the tick, check JobHandle.IsCompleted. If true, call Complete on the JobHandle and then reschedule the long-running job.
     
  7. shortwanabelaker

    shortwanabelaker

    Joined:
    Jul 10, 2018
    Posts:
    3
    How would you suggest to do this if I need to pass in native arrays, which then need to be disposed later?
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    [DeallocateOnJobCompletion] for NativeArrays, DisposeJob() for other containers (and NativeArrays in a newer Unity version than the one I am currently on).
     
  9. shortwanabelaker

    shortwanabelaker

    Joined:
    Jul 10, 2018
    Posts:
    3
    if I use [DeallocateOnJobCompletion], does that mean I don't need to call nativeArray.Dispose()? .. and therefore don't need to use job.Complete()?
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Yes to not having to call Dispose. You may or may not have to call Complete depending on your scenario.
     
  11. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    883
    So, basically a can not use JobHandle.CombineDependencies for the chains of long-running jobs? It is very sad.