Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

A single job system not running on worker thread

Discussion in 'C# Job System' started by bigdaddy69dev, Mar 14, 2022.

  1. bigdaddy69dev

    bigdaddy69dev

    Joined:
    Jul 10, 2021
    Posts:
    13
    In my project, terrain must be generated as often as it needs and the task can also be as expensive as it needs. So I decided to put the logic of preparing vertices and triangles into a job system to offload some processing on the main thread. Instead, the main thread is waiting for the job's task to complete and the worker threads are empty. I've attached the code and profiler images below. This is my very first job system writing and it seems like I'm misunderstanding something. I see some tutorials running jobs in a loop and see massive performance boost but for my case, I don't need to put the logic into the loop as I'm not going to do the same logic again and again. How can I transfer a single heavy-processing task into a worker thread so that the main thread can go on?

    Code (CSharp):
    1. private void createPlane()
    2.     {
    3.         int loopX = maxWidth + 1;
    4.         int loopZ = (maxLength * 2) + 1;
    5.         NativeArray<Vector3> lowGround = new NativeArray<Vector3>(terrainHeightEditor.lowGround.Count, Allocator.TempJob);
    6.         lowGround.CopyFrom(terrainHeightEditor.lowGround.ToArray());
    7.         NativeArray<Vector3> vertices = new NativeArray<Vector3>((loopX * loopZ) - maxLength, Allocator.TempJob);
    8.         NativeArray<int> triangles = new NativeArray<int>(maxWidth * maxLength * 12, Allocator.TempJob);
    9.  
    10.         CreateTerrainJob job = new CreateTerrainJob {
    11.             loopX = loopX,
    12.             loopZ = loopZ,
    13.             maxWidth = this.maxWidth,
    14.             maxLength = this.maxLength,
    15.             cellSize = this.cellSize,
    16.             startX = this.startX,
    17.             startZ = this.startZ,
    18.             vertices = vertices,
    19.             triangles = triangles,
    20.             lowGround = lowGround,
    21.         };
    22.         JobHandle jobHandle = job.Schedule();
    23.         jobHandle.Complete();
    24.  
    25.         Mesh mesh = new Mesh();
    26.         mesh.vertices = vertices.ToArray();
    27.         mesh.triangles = triangles.ToArray();
    28.         mesh.RecalculateNormals();
    29.         GetComponent<MeshFilter>().mesh = mesh;
    30.         // GetComponent<MeshCollider>().sharedMesh = mesh;
    31.  
    32.         lowGround.Dispose();
    33.         vertices.Dispose();
    34.         triangles.Dispose();
    35.     }
    Code (CSharp):
    1. public struct CreateTerrainJob: IJob {
    2.     public int loopX;
    3.     public int loopZ;
    4.     public int maxWidth;
    5.     public int maxLength;
    6.     public float cellSize;
    7.     public float startX;
    8.     public float startZ;
    9.     public NativeArray<Vector3> vertices;
    10.     public NativeArray<int> triangles;
    11.     public NativeArray<Vector3> lowGround;
    12.     public void Execute()
    13.     {
    14. //logic here
    15.     }
    16. }
     
  2. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,743
    Fairly sure the reason is that you call Complete() right away. That forces the job to be finished directly (obviously) before the main thread can continue. So it's probably even more efficient that the code runs on that main thread as the only alternative would be to pointlessly put the main thread to wait (aka sleep) until the worker thread finished and that naturally gains no performance improvement either.

    To solve this, you should ensure that you call Complete() in another frame or at very least in the LateUpdate of the same frame. Then the main thread has the possibility to actually do something in parallel.
     
    Last edited: Mar 14, 2022
    bigdaddy69dev and Ryiah like this.
  3. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,468
    You're close. You don't want to run it at all if you don't have to because as the documentation says it will attempt to execute the job itself on the thread which calls the Complete() function. If call it on the main thread it will run the task on the main thread before anything else.

    https://docs.unity3d.com/ScriptReference/Unity.Jobs.JobHandle.Complete.html

    Instead of calling Complete() just watch the IsCompleted boolean of the JobHandle to see when it has completed and you can start using the data.
     
    Last edited: Mar 14, 2022
    bigdaddy69dev likes this.
  4. bigdaddy69dev

    bigdaddy69dev

    Joined:
    Jul 10, 2021
    Posts:
    13
    Thanks for the replies I now understand. My main thread is unnecessarily waiting for the job to be completed because I put jobHandle.Complete() as soon as the job is scheduled. I've now checked the jobHandle.isComplete in update and update my mesh only if it is completed and now the job is running in one of the worker threads and the main thread has offloaded some processes.