Search Unity

Question Safe to call schedule update and complete job handle in background thread?

Discussion in 'Unity Transport' started by adamwilks, Oct 19, 2022.

  1. adamwilks

    adamwilks

    Joined:
    Apr 3, 2017
    Posts:
    27
    I am working on a project which is not using the Unity Job System and so according to the documentation and samples I need to call NetworkDriver.ScheduleUpdate() and JobHandle.Complete() from a component Update() on each frame. Presumably this invokes the core processing loop in the net code to do IO and queue up events.

    There are two things I am unclear on that perhaps someone could help me with;

    Firstly since the Unity Transport package is designed to work with the job system it presumably has some thread safety built in. When *not* using the job system is it safe, or even recommended, to call schedule update and complete from a background thread in order to avoid adding to the scene render time?

    Additionally if those calls are to be made on the main thread would it be optimal to make them before or after popping available events from connections?
     
  2. simon-lemay-unity

    simon-lemay-unity

    Unity Technologies

    Joined:
    Jul 19, 2021
    Posts:
    441
    I'm not 100% sure, but I don't believe it is possible to schedule jobs from anything but the main thread.

    With that said, while the documentation does point towards scheduling the update job and completing it at the same time, it only does so to make the examples easier to read. It is possible (and even recommended) to schedule the update job and only complete elsewhere in the code. This gives the opportunity for the transport to run its processing loop on a different thread, while the main thread does something else.

    Here's a very simple example of how this could be accomplished:
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Jobs;
    3. using Unity.Networking.Transport;
    4.  
    5. public class ExampleBehaviour : MonoBehaviour
    6. {
    7.     private JobHandle m_UpdateHandle;
    8.     private NetworkDriver m_Driver;
    9.  
    10.     void Update()
    11.     {
    12.         m_UpdateHandle.Complete();
    13.         // Accept new connections and pop events.
    14.      }
    15.  
    16.      void LateUpdate()
    17.      {
    18.          m_UpdateHandle = m_Driver.ScheduleUpdate();
    19.      }
    20. }
    This will allow the transport to run its processing loop in a background thread in between the
    LateUpdate
    and
    Update
    calls. You'd just have to be careful not to access the driver during this time. Of course, more complicated setups are possible (e.g. you could hook into the player loop for a more fine-grained control of when updates are scheduled and completed).

    Regarding when it is best to accept connections and pop events, I would do that immediately after completing the update. However, it technically can be done at anytime before the next
    ScheduleUpdate
    call.
     
    adamwilks likes this.
  3. adamwilks

    adamwilks

    Joined:
    Apr 3, 2017
    Posts:
    27
    That's exactly the insight I was looking for, many thanks.

    I am actually planning to hook into the player loop for some other types of system anyway so will experiment a bit with what you've suggested.
     
  4. adamwilks

    adamwilks

    Joined:
    Apr 3, 2017
    Posts:
    27
    So after a week or so of experimenting with this I now have a better understanding of the job system and regret not reading more about that before posting. In case it helps anyone else who stumbles across this I found the following profiler comparisons clear things up nicely.

    Scheduling, completion and event handling in the update:

    upload_2022-10-26_13-21-9.png

    Scheduling in late update followed by completion and event handling:

    upload_2022-10-26_13-26-24.png

    Green bars highlight the network related workloads with the client and server code running in the same instance and each having its own driver instance.

    There is very little workload happening but I think the example illustrates how the job system seems to be parallelizing things better and completing earlier in the frame which would be much more important the closer you are to being CPU bound on the main thread.

    Haven't tried inserting into the player loop yet, but would expect to see those green bars moving further toward 0ms in the frame providing more time for processing if required.

    Overall I've had a pretty good experience working with the transport package so far and the API is fairly intuitive once you get used to the idea of how a UDP based session works and how pipelines fit into the picture. I haven't tested the ReliableSequencedPipelineStage in anger yet but fully intend to.
     
    simon-lemay-unity likes this.