Search Unity

Scheduling Jobs from another thread

Discussion in 'Entity Component System' started by IanNicolades, Apr 19, 2020.

  1. IanNicolades

    IanNicolades

    Joined:
    Oct 1, 2016
    Posts:
    40
    Hi,

    I found several threads on this subject, which seem to conclude that there's no reason to ever need to do this:

    https://forum.unity.com/threads/solved-schedule-c-job-from-different-non-job-thread.546126/
    https://forum.unity.com/threads/solved-c-job-system-vs-managed-threaded-code.545360/#post-3602938

    However, my use case is slightly different. I have a simulation thread which deterministically processes all ingame events. Some of these tasks take a very long time to complete, depending upon the size of the simulation, and the ordering of these events is strictly scheduled based upon data availability from other threads and simulation parameters, including delaying the processing of sim data under specific circumstances. All of the multithreading is currently scheduled myself, and there are up to six separate threads which operate in this manner, all of which are dependent upon one another in various ways. This way, the simulation thread can run as quickly or as slowly as it needs to, and the user experience is never impacted as the rendering threads are never blocked and always have up-to-date data from the sim thread to interpolate between.

    All of this is quite straightforward to manage on its own thread. But, I would like to Jobify certain tasks to take advantage of Burst, and this is apparently incompatible with my architecture. Based on those threads, the solution appears to be "just wrap all of your managed events in Jobs and let the Job system handle them", but I don't understand how I could perform the equivalent of pausing the simulation thread in this case. For example:

    Code (CSharp):
    1. void SimThread()
    2. {
    3.     while(simRunning)
    4.     {
    5.         WaitForInput();
    6.         PerformLongComputationManuallySplitAcrossOtherThreads();
    7.         while(ThoseComputationsAreNotYetDone)
    8.              Thread.SpinWait();
    9.         PerformMoreLongComputationsBasedOnThoseResults();
    10.         SyncSomeData();
    11.         while(simPaused)
    12.             Thread.SpinWait()
    13.     }
    14. }
    An illustrative simplification of the requirements I have - the real thing is far more complex, hence my desire to replace it with Jobs in pieces at a time. It's also entirely possible that I don't fully understand how easy the solution the solution in those threads actually is to use in my scenario - in which case, please use small words for my tiny brain. :D

    Thanks!
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I would first check if calling .Run() on a job from your custom thread works. It didn't at one point, but I don't know if that is still the case. If not, you will likely have to use Burst function pointers for everything and litter [BurstCompile] all over your code.
     
  3. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547

    public static unsafe FunctionPointer<T> Unity.Burst.BurstCompiler.CompileFunctionPointer<T>T delegateMethod) where T : class
     
  4. IanNicolades

    IanNicolades

    Joined:
    Oct 1, 2016
    Posts:
    40
    Doesn't seem to work from within a custom thread:
    Code (CSharp):
    1. UnityException: CreateJobReflectionData can only be called from the main thread.
    2. Constructors and field initializers will be executed from the loading thread when loading a scene.
    3. Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
    4. Unity.Jobs.LowLevel.Unsafe.JobsUtility.CreateJobReflectionData (System.Type type, Unity.Jobs.LowLevel.Unsafe.JobType jobType, System.Object managedJobFunction0, System.Object managedJobFunction1, System.Object managedJobFunction2) (at <f38c71c86aa64e299d4cea9fb7c715e1>:0)
    5. Unity.Jobs.IJobExtensions+JobStruct`1[T].Initialize () (at <f38c71c86aa64e299d4cea9fb7c715e1>:0)
    6. Unity.Jobs.IJobExtensions.Schedule[T] (T jobData, Unity.Jobs.JobHandle dependsOn) (at <f38c71c86aa64e299d4cea9fb7c715e1>:0)
    7. SimulationManager.SimTickThread () (at Assets/Sim/simulation.cs:56)
    From this code:
    Code (CSharp):
    1. public class SimulationManager
    2. {
    3.     [BurstCompile]
    4.     struct Job : IJob
    5.     {
    6.         public void Execute()
    7.         {
    8.  
    9.         }
    10.     }
    11.  
    12.     Thread thread;
    13.     public void Init()
    14.     {
    15.         thread = new Thread(new ThreadStart(SimTickThread));
    16.         thread.Start();
    17.     }
    18.     private volatile bool threadAlive = true;
    19.  
    20.     public void SimTickThread()
    21.     {
    22.         while(threadAlive)
    23.         {
    24.             var j = new Job();
    25.             var handle = j.Schedule();
    26.             handle.Complete();
    27.         }
    28.     }
    29. }
    How would FunctionPointer help in this case? I couldn't find documentation on it.
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Function Pointers aren't jobs, but can be executed with Burst. Burst 1.3.x documentation is significantly better than the 1.2.x docs when it comes to these lesser-known features. I would look at the EntityCommandBuffer code as an example of how to use Burst function pointers directly.
     
  6. IanNicolades

    IanNicolades

    Joined:
    Oct 1, 2016
    Posts:
    40
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    You have to look at the actual Entities package source code. If you do a search in the Project window with "In Packages" selected, you will find it. EntityCommandBuffer.cs is what you are looking for.
     
  8. IanNicolades

    IanNicolades

    Joined:
    Oct 1, 2016
    Posts:
    40
    Found it, thanks! Though I missed the part about it not being compatible with Jobs. Dang! Might be too early to invest a lot of resources down this path at the moment.

    The only other solution I can think of is to wrap the scheduling of Jobs and return of NativeArrays of data in helpers that process them on the main thread and pass results back to my threads as needed, but that could result in a worst-case scenario delay of an entire frame - and going over just how many I would need to use... Not optimal. :D

    Is it known if scheduling Jobs from our own threads is on the roadmap?
     
  9. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I don't think so. I do wonder if a workaround could be wrapping every job in a FunctionPointer that calls Execute directly, and invoking that FunctionPointer from your custom threads.
     
  10. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    In fact, You can't wrap a job in a function pointer and boost it with CompileFunctionPointer.
    CompileFunctionPointer can only compile non-generic static function. with the static function and the outer class/struct marked with [BurstCompile], also the function can not have return type and ref/in parameter according to burst doc.
    https://docs.unity3d.com/Packages/com.unity.burst@1.3/manual/index.html#function-pointers

    for jobs in Unity.Jobs the Scheduler is doing the dirty job by getting the pointer of Job structs and pass those pointers to worker thread.
    You can wrap a delegate function in a JobStruct with function pointer, without burst compile. But it has no meaning in this case.
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I meant doing this:
    Code (CSharp):
    1. [BurstCompile]
    2. public static void ExecuteCustomJob(CustomJob job)
    3. {
    4.     job.Execute();
    5. }
    Is it an actual job? No. But does it get the effect of writing a job and running it with Burst? Yes, at the cost of quite a bit of boilerplate per job. But you could probably codegen all of that boilerplate to detect every IJob and create a wrapper for it.
     
    Lieene-Guo likes this.
  12. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    That's an effective way of burst unmanaged type functions
     
  13. PitaCuParizel

    PitaCuParizel

    Joined:
    Feb 25, 2017
    Posts:
    7
    I'm trying to do something like this, but it seems like it doesn't use Burst when doing this. Calling .Complete() on the job takes 14ms, but using this burst function and calling .Execute() takes 700. Is there something I'm missing? I just want to be able to .Execute() jobs on my own background threads, while taking advantage of Burst.
     
  14. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    2021.1 and Burst 1.5 support direct calls to static methods which get Bursted. Otherwise, make sure you are using a Burst function pointer and check the Burst Inspector to ensure there's no errors in compilation.
     
    vildauget likes this.
  15. PitaCuParizel

    PitaCuParizel

    Joined:
    Feb 25, 2017
    Posts:
    7
    I did succeed in calling burst functions, but they cannot fill the roles of a job, too many limitations. I'm talking about having a Burst-Compiled job, sending it to my background thread that will then execute the job on said background thread. Should be technically possible, but only if Unity allows it.