Limitations of multithreading and jobs system
I'm currently working on an artificial intelligence system for simple animals with a number of basic behaviors. (Flocking, navigation, object avoidance, predator/prey).
As I started to implement more behaviors the logic was quickly turning into spaghetti code and quickly becoming inefficient. Originally a controller class handed everything from variables, inputs, agent creation, animation logic, object avoidance, parent/target movement, flocking calls, and predator/prey calls.
Most of the logic was handled in the controller class with countless if statements wrapped in a massive Update loop. Some of the flocking logic, predator prey, and navigational logic was threaded to another class. With 100 agents under a single controller, while running the scene in editor the scene run between 15 and 30 fps.
Seeing the hit to performance I decided it was time for a refactor and to implement a better architecture. The idea was to break up the massive controller class into a number of smaller classes. The manual variables where all placed into an interface class. The agent creation was handled by an agent creation class. And the base logic would be handled in a main class. The main class would call the other classes, move the parent/target object, update variables as needed, handle the garbage collection, and begin an AI State Machine. The AI State Machine would be its own thread which would determine which state each agent should be in, then act accordingly. Each state would run its own logic from a subclass of the state machine, ensuring the correct behavior is seen for each state.
The first attempt was to accomplish this through native c# threading. Things functioned well until I needed to plug in the logic to determine object avoidance. Since object avoidance needed to know the agents position and heading (Vector3’s), it required accessing the Unity API in a thread. This isn’t supported, thus is not currently possible. I learned that Unity’s API is not ‘thread safe’ and would throw errors any time I attempted to reference a Unity specific data type... Unity 2018 had the newly implemented ‘Jobs’ system, that was quoted to be ‘Unity’s solution to multithreading’.
The second attempt was to change the logic to use the new Unity Jobs system instead of the native c# threading logic. This would allow me to use Unity’s API in threaded logic. This seemed to be a fix, until it was discovered that Unity’s job system would not support the passing of an object reference into a unity job (c# native threading did support passing an object into a thread). This would entirely defeat the purpose of threading, since I would not be able to pass a list of agents and their values into a job/threaded state machine. I could only pass set values into the job. In all the documentation I saw, it was stated that the job system was meant to work with Unity’s new ‘Component’ system, however the Component system has yet to be released, and I am unsure how that would fix or change the object reference problem.
Attempt three: I spent a few hours trying to combine both treading and the unity jobs system into my logic, the idea was to have the state machine run as a thread, then each state be run as a job, with the Unity API only needing to be referenced in the job, and the thread state machine keeping the object reference separate. However, the code quickly devolved into a nonfunctional mess, and the idea was quickly abandoned.
Attempt four: Looking through Unity’s asset store, I found something called Thread Ninja. It was highly rated and had reviews as recent as a month ago. It claimed to be a solution to Unity’s limitation to threading. The way it works is by combining a coroutine with a background thread, I could bring that coroutine to the main thread and push other logic to background then push the coroutine into the background and bring the main logic back to the main thread once the coroutine had completed. This allowed me to follow the original plan, having my main class call the other classes, spawn the agents, then set the state machine to run as a coroutine, which would be pushed to the main thread once it needed to access Unity’s API, and then return to a background thread once done. However, It turns out Thread Ninja isn’t true threading, everything is still being run via the main thread just with either the coroutine logic or the main logic taking priority. The outcome changed from having the logic run between 15 and 30 fps, to having the logic run over 100fps for 5 frames, then slowing to 10fps for a single frame when the courtine was pushed to the front for its logic, effectively stuttering the application.
Now I'm at a stand-still. I'm out of ideas of how I can refactor the logic to fully utilize a multithreaded architecture. Does anyone have any ideas how I might be able to approach the problem differently? Has anyone worked with the new ECS system, will it fix my problem of being able to pass object references into a job? I'm open to any advice on how best to proceed.
Answer by mubashar0612 · Mar 30, 2019 at 01:30 PM
You can use the approach used in the linked video using multithreading.Pass the callback to thread where you can do all your calculations and set certain data based on those calculations(like positions, rotations etc) and when the calculation is finished you can enqueue the callback along with data in ThreadQueue and on each update you can check ThreadQueue count and then dequeue and call the callback and pass that data to callback which will run on the main thread where you can access unity's api's. Here is the explanation from 2:40 https://www.youtube.com/watch?v=f0m73RsBik4&list=PLFt_AvWsXl0eBW2EiBtl_sxmDtSgZBxB3∈dex=8