Search Unity

Multithreading In Unity

Discussion in 'Scripting' started by SevenHams, Mar 14, 2015.

  1. SevenHams

    SevenHams

    Joined:
    Dec 27, 2012
    Posts:
    67
    I've poked around here and there and one thing I heard time and time again is a pretty simple "Unity is not thread safe." Things that inherit from Monobehaviour are not thread safe and using things like transforms are also not thread safe. I was thinking of this puzzle because I'm writing some rather complex AI for a project that makes Unity slow to a crawl. There was just too much going on to get a good frame rate. The obvious answer was multithreading but how do you add threads to a platform that isn't thread safe?

    You do the calculations totally separately from the main thread and send them to a storage place. That's how. This is the solution I came up with. I'm posting it in the hopes that it will be useful. A few things though; most personal projects aren't likely to need this and parallel processing is a pain in the butt no matter how you slice it. Even so, using nothing more than the standard C# threading this can be quite useful. The solution was to create a pair of buffers that hold updates. This example uses transforms. I have used this for instantiating and deleting game objects as well. I imagine it works for other things as well.

    This is unlikely to be perfect of course but it does work.

    Note that you cannot use GetComponent outside of the main thread. Any references that you use must be acquired during the main thread. This is where keeping references to things you'll want to update is quite useful. The references can be passed around just fine.

    Also note that I have only tested this with ONE extra thread. This works perfectly fine when generating a bunch of updates of various types in sequence in a secondary thread. I have not tested this for more. However, I imagine that you could create a second pair of buffers to run for another thread. This code is also still a bit unsafe but I have yet to see a collision or have issues. So long as the calculations happen outside of adding the updates adding the update is very fast.

    Code (CSharp):
    1. public class TransformUpdate
    2. {
    3.     public GameObject gobUpdate;
    4.  
    5.     public Vector3 v3Update;
    6.  
    7.     public TransformUpdate(GameObject pgobUpdate, Vector3 pv3Update)
    8.     {
    9.         gobUpdate = pgobUpdate;
    10.         v3Update = pv3Update;
    11.     }
    12. }
    This is a simple class that will be put into lists in another class. This just stores an object and the vector it will be moved to later. This can be done outside of the main thread as long as you're putting a reference in and not creating a new object or something like that. Once again, so long as you're sending it a reference to a game object that already exists it can be passed around outside of the main thread.

    Within a static class that will track what needs updating and whatnot add this:

    Code (CSharp):
    1.  
    2.     static bool bUpdatingListOne = false;
    3.  
    4.     static List<TransformUpdate> lstTransformUpdatesOne = new List<TransformUpdate>();
    5.     static List<TransformUpdate> lstTransformUpdatesTwo = new List<TransformUpdate>();
    6.  
    These are your buffers. At any time only one of them will be receiving updates and the other one will be getting updated by the main thread.

    Code (CSharp):
    1.  
    2.    public static void UpdateTransforms()
    3.     {
    4.         if (bUpdatingListOne == true)
    5.         {
    6.             for (int i = lstTransformUpdatesOne.Count - 1; i >= 0; i--)
    7.             {
    8.                 lstTransformUpdatesOne[i].gobUpdate.transform.position = lstTransformUpdatesOne[i].v3Update;
    9.                 lstTransformUpdatesOne.Remove(lstTransformUpdatesOne[i]);
    10.             }
    11.             bUpdatingListOne = false;
    12.         }
    13.  
    14.         else if (bUpdatingListOne == false)
    15.         {
    16.             for (int i = lstTransformUpdatesTwo.Count - 1; i >= 0; i--)
    17.             {
    18.                 lstTransformUpdatesTwo[i].gobUpdate.transform.position = lstTransformUpdatesTwo[i].v3Update;
    19.                 lstTransformUpdatesTwo.Remove(lstTransformUpdatesTwo[i]);
    20.             }
    21.             bUpdatingListOne = true;
    22.         }
    23. }
    Here is what you point Unity to. An Update call somewhere should call this method. It will update anything put into whatever buffer is not having updates added to it.

    To add stuff...

    Code (CSharp):
    1.  
    2.     public static void AddTransformUpdate(GameObject pgobUpdate, Vector3 pv3Update)
    3.     {
    4.         if (bUpdatingListOne == true)
    5.             lstTransformUpdatesTwo.Add(new TransformUpdate(pgobUpdate, pv3Update));
    6.         else
    7.             lstTransformUpdatesOne.Add(new TransformUpdate(pgobUpdate, pv3Update));
    8.     }
    9.  
    This adds the new updates to whichever update set is not being added to.

    Taken together each time Unity updates it only runs through ONE list each update frame. Once it is done it switches to the other and waits for the next update call to start before running the other. Meanwhile whichever one Unity is NOT running through has new updates added to it.