Search Unity

Set mesh data inside job

Discussion in 'Entity Component System' started by Radu392, Oct 25, 2019.

  1. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    First, an explanation on why I want to do that.

    I have a bursted job that iterates through many thousands entities and computes vertices, uvs and triangles and stores them in their respective native arrays. If I use a Complete() statement on this job, it takes ~1.5ms to complete. Without Complete(), it takes almost no time but the job is still getting done! This is the only place in my code that still has a Complete() statement and I want to get rid of it. The job itself can't be optimized any further either. However, I need to call Complete() so I can set the mesh data from the main thread because neither this

    mesh.SetVertices(verticesList);

    Nor this

    mesh.vertices = vertices;

    Works in a thread as both throw a UnityException: get_canAccess can only be called from the main thread. in the mesh class.

    mesh.Clear() throws a similar error.

    So what I want is to schedule my computation job then create a new job that depends on the former which simply sets the new mesh data onto the mesh, instead of calling Complete() and do so on the main thread.

    I looked everywhere on the internet but I couldn't find any example on how to set mesh data from inside a thread. I've tried dabbling with mesh.SetVertexBufferData but I have no idea if that would work inside a thread and I can't figure it out myself with just the example given in the docs. Plus it looks like that works only for vertices, not triangles and uvs. I've also looked at Tertle's various mesh examples passing dynamic buffers onto the mesh directly, but that has to be done from the main thread as well.

    Is setting mesh data from within a job not possible? If it's not, how can I avoid the Complete() call and somehow have a sort of callback to the job so that I can pass the data onto the mesh on the main thread once the computational job complete? And if it is possible, isn't there some sort of pointer magic that can be used?
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    You can't. One option is to separate the mesh manipulation into 2 systems.

    1.
    MeshManipulationSystem : JobComponentSystem
    Manipulate your mesh arrays

    2.
    [UpdateAfter(typeof(MeshManipulationSystem))]
    MeshApplySystem : ComponentSystem
    Apply your arrays to the meshes
     
    Radu392 likes this.
  3. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    I haven't thought of that, great idea! I'll have to try it out tomorrow.

    Side question: When using [UpdateAfter(typeof(MeshManipulationSystem))] on MeshApplySystem, will the MeshApplySystem be executed right after the manipulation system, or *sometime* after it? I would think putting other jobs in front of the manipulation system before reaching the Apply system would be optimal. If it's right after, I might have to look into manually ordering my systems.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Sometime after. The scheduler will organize dependencies and as it's a componentsystem it'll usually be executed near end of frame to reduce sync points. If it was executed right after it'd be pretty much the same as calling Complete so that's not much use.
     
  5. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    Hmm it was a nice try, but it didn't work. It looks like the second system is still waiting on the first one to finish. It's actually worse of a performance by 1ms now, but only in the build. In the editor it remained about the same. I tried deep profiling it and it seems that these 3 lines are the problem:

    Code (CSharp):
    1.                 DynamicBuffer<Vector3> vertices = entityManager.GetBuffer<Vert>(bufferHolder).Reinterpret<Vector3>();
    2.                 DynamicBuffer<Vector2> uv = entityManager.GetBuffer<UV>(bufferHolder).Reinterpret<Vector2>();
    3.                 DynamicBuffer<int> triangles = entityManager.GetBuffer<Triangle>(bufferHolder).Reinterpret<int>();




    But at the same time, there's something weird going on in the deep profiler. When I look at other frames, I get another bottleneck. In the pic below, you can see that it's still the 'RenderSystemMainThreadSync' that's causing the sync to trigger, but that now the bottle neck is my 'HandleDeathAnimationJob'



    Which is weird because the code for that job is just this:


    Code (CSharp):
    1.             HandleDeathAnimationJob handleDeathAnimationJob = new HandleDeathAnimationJob {
    2.                 deltaTime = Time.deltaTime,
    3.                 destroyedPos = new float3(-500, -500, 0)
    4.             };
    5.             JobHandle jh1 = handleDeathAnimationJob.Schedule(this, inputDeps);
    6.             return jh1;
    7.         }
    8.  
    9.  
    10.         struct HandleDeathAnimationJob : IJobForEachWithEntity<Stats, Unit, Translation> {
    11.             [ReadOnly] public float deltaTime;
    12.             [ReadOnly] public float3 destroyedPos;
    13.             public void Execute(Entity entity, int index, ref Stats stats, [ReadOnly] ref Unit unit, ref Translation trans) {
    14.                 if (stats.markedForDestroy) {
    15.                     if (stats.currentDestroyTimer > 0) {
    16.                         stats.currentDestroyTimer -= deltaTime;
    17.                     } else {
    18.                         trans.Value = destroyedPos;
    19.                         stats.disabled = true;
    20.                         stats.markedForDestroy = false;
    21.                     }
    22.                 }
    23.             }
    24.         }
    There aren't even any markedForDestroy entities, yet the profiler thinks it's that job that takes the longest. What my theory is for that is that this is the latest job to be executed in a chain of jobs, so it depends on a more complex job above in the chain. But the profiler can't say what it is so it just marks that last job to execute as the culprit for performance.

    The timeline deep profile doesn't show anything other than the main thread for the build.

    One thing is for sure, the previous implementation was more performant. I'll try again this time without using dynamic buffers to store the mesh data and I'll report back.
     
  6. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    Alright so I did that. I get the same error as when I tried to use a JobComponentSystem with GetBufferFromEntity<Vert>();. There's still a dependency error even with the 'UpdateAfter' directive above the system, so I have to call Complete() no matter what.

    Curiously, it looks like having at least one Complete() in your code is better than having none at all, as the job system seems to go haywire and do jobs in random order without any Complete() statement.
     
    Last edited: Oct 26, 2019