- Home /
How to periodically pull output from an IJobParrallel or is there a better way to do this?
I've built a voxel engine prototype that generates voxel data into chunks and turns those chunks into visual meshes. My current implementation uses regular C# threads, but I'd like to try porting it over to Unity's Job system given how optimized it is.
The way it works
The map is a 2D array of Chunk structs which itself contains a 3D array of Voxel structs to form the geometry of the world. There's 2 threads that work with the map and each works on a queue of Chunks. The world builder thread pulls a Chunk from it's input queue and procedurally generated each voxel before passing the Chunk to the mesh builder queue. The mesh builder thread pulls a Chunk from its input queue and builds vertexes and UV array before passing it to the output queue. Finally, at the end of the frame during LateUpdate
, the main thread pops everything from the output queue (technically it takes as many as it can without going over the frame time) and turns the vertex arrays into GameObjects with meshes.
My attempt at Jobifiction
I would think that I'd combine thw threads into a single IJobParrallel to do both the world building and subsequently build the mesh data and then output said data via a NativeArray to be used by the main thread. The issue being that this "Builder Job" would have a potentially multi-frame workload that I would like to basically "stream" the output of to my main thread (ie make a GameObject for each Chunk as its built). From what I see, Jobs are kind of "all or nothing" in that you cannot access the NativeArray output until you call Job.Complete() on the main thread. During LastUpdate for a given frame I'd like to pull everything the Job has done so far, but I'm not sure how to accomplish that.
Is there a way to achieve what I'm looking for?
Answer by troien · Mar 17, 2019 at 11:34 AM
I believe that what you want is not possible by design. The whole idea of a job is that you tell it to start doing something, do something else and call complete when you are able to process it's result.
Below a few ideas. None of my ideas are tested for your scenario, but they might be worth a shot or point you in the right direction ;)
If you could access the result while it is still modifying that result (On a different thread), you can get into a lot of problems :p You might be able to acces your nativearray outside of your job by using the NativeDisableContainerSafetyRestrictionAttribute or NativeDisableParallelForRestriction. But note that those safety checks are done for a reason, so make sure you know what you are doing before disabling them. Bugs can happen and Unity can crash if you make mistakes here :p
Another option maybe (haven't tested this) you can split up your work into multiple jobs (i.e. if you have a for loop, job#1 does index 0-99, job#2 100-200, etc.). schedule all those jobs so that they depend on each other (meaning job2 waits for job1 to finish before starting). But store intermediate job handles to each of them. If that last part is possible and works the way I expect it to, you can schedule a lot of jobs at once. And then in each frame, call complete on the first x scheduled jobs.
If you don't mind using stuff that is still in beta (Not production ready) and requires (possibly a lot) of rewrite of existing code. Did you consider using the ecs approach? Because in that way, you never have to create GameObjects to start with. Which at the same time means it is way more performant. And I believe it is also possible to use the EntityCommandBuffer to create Enities inside a job, which is possibly similar to what you want to do with gameobjects.
If you want to use ecs, I would advise watching the tutorial videos (Note however that the setup is different now, videos are outdated on that part, use the 'Entities' package from the packagemanager in unity). Unity has some examples on their github aswell.
There might be other (better) options aswell, which is why I posted this initially as a comment ;)
@troien Interesting suggestions. I particularly like the disable safety restrictions attribute allowing access to a NativeArray before calling Complete. I've dealt with threads extensively so I'm vigilant when it comes to thread safety. I really like the concept of the new ECS paradigm, but I feel like I'll lose many hours to learning a vastly different system with little doc just to potentially have things change under my feet, so I'll wait on that.
I came up with a variation of your "multiple jobs" approach. I could spawn a job for each chunk and, during LateUpdate, loop through them and check for Job.IsComplete == true and call Job.Complete on them (possibly breaking the loop if I'm running out of frame time).
It'd be useful if a job could signal the main thread via an event or something when its completed its work. Useful in multi-frame job scenarios.
@troien If you post your comment as an answer I will mark it as accepted.
Your answer
Follow this Question
Related Questions
New Unity 2020.2.6 automatically uses multiple threads? 0 Answers
Help with Optimizing Voxel Code? 1 Answer
Run return method on new thread? 2 Answers
Multiple Cars not working 1 Answer