As The last ship sailed towards the distant horizon I sat there watching on a rock My mind slowly drifting away Forming into my... Dreamtale
Unity utils MultiThread Tool Loom
2017-12-05 / 3 min read

Threads on a Loom

Loom lets you easily run code on another thread and have that other thread run code on the main game thread when it needs to.

There are only two functions to worry about:

  • RunAsync(Action) which runs a set of statements on another thread
  • QueueOnMainThread(Action, [optional] float time) - which runs a set of statements on the main thread (with an optional delay).

You access Loom using Loom.Current - it deals with creating an invisible game object to interact with the games main thread.

using UnityEngine;  
using System.Collections;  
using System.Collections.Generic;  
using System;  
using System.Threading;  
using System.Linq;  
  
public class Loom : MonoBehaviour  
{  
    public static int maxThreads = 8;  
    static int numThreads;  
      
    private static Loom _current;  
    private int _count;  
    public static Loom Current  
    {  
        get  
        {  
            Initialize();  
            return _current;  
        }  
    }  
      
    void Awake()  
    {  
        _current = this;  
        initialized = true;  
    }  
      
    static bool initialized;  
      
    static void Initialize()  
    {  
        if (!initialized)  
        {  
          
            if(!Application.isPlaying)  
                return;  
            initialized = true;  
            var g = new GameObject("Loom");  
            _current = g.AddComponent<Loom>();  
        }  
              
    }  
      
    private List<Action> _actions = new List<Action>();  
    public struct DelayedQueueItem  
    {  
        public float time;  
        public Action action;  
    }  
    private List<DelayedQueueItem> _delayed = new  List<DelayedQueueItem>();  
  
    List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();  
      
    public static void QueueOnMainThread(Action action)  
    {  
        QueueOnMainThread( action, 0f);  
    }  
    public static void QueueOnMainThread(Action action, float time)  
    {  
        if(time != 0)  
        {  
            lock(Current._delayed)  
            {  
                Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action});  
            }  
        }  
        else  
        {  
            lock (Current._actions)  
            {  
                Current._actions.Add(action);  
            }  
        }  
    }  
      
    public static Thread RunAsync(Action a)  
    {  
        Initialize();  
        while(numThreads >= maxThreads)  
        {  
            Thread.Sleep(1);  
        }  
        Interlocked.Increment(ref numThreads);  
        ThreadPool.QueueUserWorkItem(RunAction, a);  
        return null;  
    }  
      
    private static void RunAction(object action)  
    {  
        try  
        {  
            ((Action)action)();  
        }  
        catch  
        {  
        }  
        finally  
        {  
            Interlocked.Decrement(ref numThreads);  
        }  
              
    }  
      
      
    void OnDisable()  
    {  
        if (_current == this)  
        {  
              
            _current = null;  
        }  
    }  
      
      
  
    // Use this for initialization  
    void Start()  
    {  
      
    }  
      
    List<Action> _currentActions = new List<Action>();  
      
    // Update is called once per frame  
    void Update()  
    {  
        lock (_actions)  
        {  
            _currentActions.Clear();  
            _currentActions.AddRange(_actions);  
            _actions.Clear();  
        }  
        foreach(var a in _currentActions)  
        {  
            a();  
        }  
        lock(_delayed)  
        {  
            _currentDelayed.Clear();  
            _currentDelayed.AddRange(_delayed.Where(d=>d.time <= Time.time));  
            foreach(var item in _currentDelayed)  
                _delayed.Remove(item);  
        }  
        foreach(var delayed in _currentDelayed)  
        {  
            delayed.action();  
        }  
          
          
          
    }  
}